From 06d1b6eaa4eeb8536c852f5df3bee751b4e77763 Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 1 Dec 2023 18:18:24 +0800 Subject: [PATCH 01/20] feat: add user login --- api/api_impl/impl.go | 1 + api/jiaozifs.gen.go | 249 ++++++++++++++++++++++++++++++++++++----- api/swagger.yml | 50 +++++++++ auth/basic_auth.go | 51 +++++++++ auth/jwt_login.go | 27 +++++ auth/types.go | 7 ++ config/config.go | 5 + config/default.go | 3 + controller/user_ctl.go | 39 +++++++ go.mod | 15 +-- go.sum | 26 +++-- models/user.go | 11 ++ models/user_test.go | 4 + 13 files changed, 438 insertions(+), 50 deletions(-) create mode 100644 auth/jwt_login.go create mode 100644 auth/types.go create mode 100644 controller/user_ctl.go diff --git a/api/api_impl/impl.go b/api/api_impl/impl.go index 1f759ae4..060b6d4f 100644 --- a/api/api_impl/impl.go +++ b/api/api_impl/impl.go @@ -13,4 +13,5 @@ type APIController struct { controller.VersionController controller.ObjectController + controller.UserController } diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index eb5e6000..32c92095 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -33,6 +33,15 @@ const ( Object ObjectStatsPathType = "object" ) +// AuthenticationToken defines model for AuthenticationToken. +type AuthenticationToken struct { + // Token a JWT token that could be used to authenticate requests + Token string `json:"token"` + + // TokenExpiration Unix Epoch in seconds + TokenExpiration *int64 `json:"token_expiration,omitempty"` +} + // ObjectStats defines model for ObjectStats. type ObjectStats struct { Checksum string `json:"checksum"` @@ -121,9 +130,18 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } +// LoginJSONBody defines parameters for Login. +type LoginJSONBody struct { + Password string `json:"password"` + Username string `json:"username"` +} + // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody +// LoginJSONRequestBody defines body for Login for application/json ContentType. +type LoginJSONRequestBody LoginJSONBody + // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -209,6 +227,11 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // LoginWithBody request with any body + LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } @@ -261,6 +284,30 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, pa return c.Client.Do(req) } +func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetVersionRequest(c.Server) if err != nil { @@ -560,6 +607,46 @@ func NewUploadObjectRequestWithBody(server string, repository string, params *Up return req, nil } +// NewLoginRequest calls the generic Login builder with application/json body +func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewLoginRequestWithBody(server, "application/json", bodyReader) +} + +// NewLoginRequestWithBody generates requests for Login with any type of body +func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/user/login") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewGetVersionRequest generates requests for GetVersion func NewGetVersionRequest(server string) (*http.Request, error) { var err error @@ -642,6 +729,11 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) } @@ -731,6 +823,28 @@ func (r UploadObjectResponse) StatusCode() int { return 0 } +type LoginResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AuthenticationToken +} + +// Status returns HTTPResponse.Status +func (r LoginResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r LoginResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetVersionResponse struct { Body []byte HTTPResponse *http.Response @@ -789,6 +903,23 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + // GetVersionWithResponse request returning *GetVersionResponse func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { rsp, err := c.GetVersion(ctx, reqEditors...) @@ -872,6 +1003,32 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -912,6 +1069,9 @@ type ServerInterface interface { // (POST /repositories/{repository}/objects) UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) + // perform a login + // (POST /user/login) + Login(w *JiaozifsResponse, r *http.Request) // return program and runtime version // (GET /version) GetVersion(w *JiaozifsResponse, r *http.Request) @@ -944,6 +1104,12 @@ func (_ Unimplemented) UploadObject(w *JiaozifsResponse, r *http.Request, reposi w.WriteHeader(http.StatusNotImplemented) } +// perform a login +// (POST /user/login) +func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // return program and runtime version // (GET /version) func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { @@ -1230,6 +1396,21 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetVersion operation middleware func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1372,6 +1553,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repositories/{repository}/objects", wrapper.UploadObject) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/user/login", wrapper.Login) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) @@ -1382,37 +1566,40 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "Gus4chqDEeO7Q764Wfw7AAD//58N5ajGGQAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 1ab95d18..579eea80 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -37,6 +37,18 @@ components: in: cookie name: saml_auth_session schemas: + AuthenticationToken: + type: object + required: + - token + properties: + token: + description: a JWT token that could be used to authenticate requests + type: string + token_expiration: + type: integer + format: int64 + description: Unix Epoch in seconds VersionResult: type: object required: @@ -379,3 +391,41 @@ paths: description: NotFound 420: description: too many requests + /user/login: + post: + tags: + - user + operationId: login + summary: perform a login + security: [] # No authentication + requestBody: + content: + application/json: + schema: + type: object + required: + - username + - password + properties: + username: + type: string + password: + type: string + responses: + 200: + description: successful login + headers: + Set-Cookie: + schema: + type: string + example: "access_token=abcde12356; Path=/; HttpOnly" + content: + application/json: + schema: + $ref: "#/components/schemas/AuthenticationToken" + 401: + description: Unauthorized ValidationError + 420: + description: too many requests + default: + description: Internal Server Error \ No newline at end of file diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 8832b06d..b5f83089 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -1 +1,52 @@ package auth + +import ( + "context" + "time" + + "github.com/go-openapi/swag" + logging "github.com/ipfs/go-log/v2" + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "golang.org/x/crypto/bcrypt" +) + +var log = logging.Logger("auth") + +type Login struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { + ctx := context.Background() + // Get user encryptedPassword by username + ep, err := userRepo.GetEPByName(ctx, l.Username) + if err != nil { + log.Errorf("username err: %s", err) + return token, err + } + + // Compare ep and password + err = bcrypt.CompareHashAndPassword([]byte(ep), []byte(l.Password)) + if err != nil { + log.Errorf("password err: %s", err) + return token, err + } + // Generate user token + loginTime := time.Now() + expires := loginTime.Add(expirationDuration) + secretKey := config.Auth.SecretKey + + tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) + if err != nil { + log.Errorf("generate token err: %s", err) + return token, err + } + + token.Token = tokenString + token.TokenExpiration = swag.Int64(expires.Unix()) + + return token, nil +} diff --git a/auth/jwt_login.go b/auth/jwt_login.go new file mode 100644 index 00000000..e1373f6a --- /dev/null +++ b/auth/jwt_login.go @@ -0,0 +1,27 @@ +package auth + +import ( + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" +) + +const ( + LoginAudience = "login" +) + +// GenerateJWTLogin creates a jwt token which can be used for authentication during login only, i.e. it will not work for password reset. +// It supports backward compatibility for creating a login jwt. The audience is not set for login token. Any audience will make the token +// invalid for login. No email is passed to support the ability of login for users via user/access keys which don't have an email yet +func GenerateJWTLogin(secret []byte, userID string, issuedAt, expiresAt time.Time) (string, error) { + claims := &jwt.StandardClaims{ + Id: uuid.NewString(), + Audience: LoginAudience, + Subject: userID, + IssuedAt: issuedAt.Unix(), + ExpiresAt: expiresAt.Unix(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(secret) +} diff --git a/auth/types.go b/auth/types.go new file mode 100644 index 00000000..aaea0caf --- /dev/null +++ b/auth/types.go @@ -0,0 +1,7 @@ +package auth + +import "time" + +const ( + expirationDuration = time.Hour +) diff --git a/config/config.go b/config/config.go index 0f034e2c..5494619f 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ type Config struct { Log LogConfig `mapstructure:"log"` API APIConfig `mapstructure:"api"` Database DatabaseConfig `mapstructure:"database"` + Auth AuthConfig `mapstructure:"auth"` Blockstore BlockStoreConfig `mapstructure:"blockstore"` } @@ -32,6 +33,10 @@ type DatabaseConfig struct { Debug bool `mapstructure:"debug"` } +type AuthConfig struct { + SecretKey []byte `mapstructure:"secretKey"` +} + func InitConfig() error { // Find home directory. home, err := os.UserHomeDir() diff --git a/config/default.go b/config/default.go index 47d6d8bf..b1709816 100644 --- a/config/default.go +++ b/config/default.go @@ -25,4 +25,7 @@ var defaultCfg = Config{ AllowedExternalPrefixes []string }{Path: DefaultLocalBSPath, ImportEnabled: false, ImportHidden: false, AllowedExternalPrefixes: nil}), }, + Auth: AuthConfig{ + SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION"), + }, } diff --git a/controller/user_ctl.go b/controller/user_ctl.go new file mode 100644 index 00000000..3dfb6bcd --- /dev/null +++ b/controller/user_ctl.go @@ -0,0 +1,39 @@ +package controller + +import ( + "encoding/json" + "net/http" + + "github.com/jiaozifs/jiaozifs/api" + "github.com/jiaozifs/jiaozifs/auth" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "go.uber.org/fx" +) + +type UserController struct { + fx.In + + UserRepo models.IUserRepo + Config *config.Config +} + +func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { + // Decode requestBody + var login auth.Login + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&login); err != nil { + w.RespError(err) + return + } + + // Perform login + resp, err := login.Login(A.UserRepo, A.Config) + if err != nil { + w.RespError(err) + return + } + + // resp + w.RespJSON(resp) +} diff --git a/go.mod b/go.mod index 9147bc05..295405ca 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,9 @@ require ( github.com/getkin/kin-openapi v0.118.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + github.com/go-openapi/swag v0.22.4 github.com/go-test/deep v1.1.0 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.4.0 github.com/hnlq715/golang-lru v0.4.0 @@ -47,6 +49,7 @@ require ( github.com/uptrace/bun/driver/pgdriver v1.1.16 github.com/uptrace/bun/extra/bundebug v1.1.16 go.uber.org/fx v1.20.1 + golang.org/x/crypto v0.16.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/oauth2 v0.13.0 google.golang.org/api v0.147.0 @@ -90,7 +93,6 @@ require ( github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/swag v0.22.4 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.0.0 // indirect @@ -158,13 +160,12 @@ require ( go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect diff --git a/go.sum b/go.sum index 6e6f6997..5897a877 100644 --- a/go.sum +++ b/go.sum @@ -185,6 +185,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -497,8 +499,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -536,8 +538,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -572,8 +574,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -596,8 +598,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -643,8 +645,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -709,8 +711,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/models/user.go b/models/user.go index 87996a0b..b6132f65 100644 --- a/models/user.go +++ b/models/user.go @@ -26,6 +26,8 @@ type User struct { type IUserRepo interface { Get(ctx context.Context, id uuid.UUID) (*User, error) Insert(ctx context.Context, user *User) (*User, error) + + GetEPByName(ctx context.Context, name string) (string, error) } var _ IUserRepo = (*UserRepo)(nil) @@ -50,3 +52,12 @@ func (userRepo *UserRepo) Insert(ctx context.Context, user *User) (*User, error) } return user, nil } + +func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, error) { + var ep string + return ep, userRepo.DB.NewSelect(). + Model((*User)(nil)).Column("encrypted_password"). + Where("name = ?", name). + Scan(ctx, &ep) + +} diff --git a/models/user_test.go b/models/user_test.go index 40468bd3..c0a6abd9 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -63,4 +63,8 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel.UpdatedAt, user.UpdatedAt, dbTimeCmpOpt)) + + ep, err := repo.GetEPByName(ctx, newUser.Name) + require.NoError(t, err) + require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) } From 80f865cb6be0c91bac7d42f68da409036dea951a Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 1 Dec 2023 19:17:42 +0800 Subject: [PATCH 02/20] fix: fix type of userrepo --- controller/user_ctl.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 3dfb6bcd..f96af687 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -14,7 +14,7 @@ import ( type UserController struct { fx.In - UserRepo models.IUserRepo + UserRepo *models.IUserRepo Config *config.Config } @@ -28,7 +28,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } // Perform login - resp, err := login.Login(A.UserRepo, A.Config) + resp, err := login.Login(*A.UserRepo, A.Config) if err != nil { w.RespError(err) return From 3c6bef5f76eb26bcee0b46db49f3be2938123e1c Mon Sep 17 00:00:00 2001 From: zjy Date: Fri, 1 Dec 2023 19:28:23 +0800 Subject: [PATCH 03/20] fix: add end of line to swagger.yml --- api/swagger.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/swagger.yml b/api/swagger.yml index 579eea80..254b25f9 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -391,6 +391,7 @@ paths: description: NotFound 420: description: too many requests + /user/login: post: tags: @@ -428,4 +429,4 @@ paths: 420: description: too many requests default: - description: Internal Server Error \ No newline at end of file + description: Internal Server Error From 5789e7429a81c95b23e7b450625a58cd749df070 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 10:17:54 +0800 Subject: [PATCH 04/20] fix: update url name --- api/swagger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/swagger.yml b/api/swagger.yml index 254b25f9..8d66d9a1 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -392,7 +392,7 @@ paths: 420: description: too many requests - /user/login: + /auth/login: post: tags: - user From 0a88031ce746476f251c23dafd12ad1b00178443 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 15:20:05 +0800 Subject: [PATCH 05/20] feat: add user registration --- api/jiaozifs.gen.go | 553 +++++++++++++++++++++++++++-------------- api/swagger.yml | 98 +++++++- auth/basic_auth.go | 41 +++ auth/types.go | 1 + controller/user_ctl.go | 17 ++ models/user.go | 9 + 6 files changed, 533 insertions(+), 186 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 32c92095..65344397 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -76,6 +76,18 @@ type ObjectStatsPathType string // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string +// RegistrationMsg defines model for RegistrationMsg. +type RegistrationMsg struct { + Message string `json:"message"` +} + +// UserRegisterInfo defines model for UserRegisterInfo. +type UserRegisterInfo struct { + Email openapi_types.Email `json:"email"` + Password string `json:"password"` + Username string `json:"username"` +} + // VersionResult defines model for VersionResult. type VersionResult struct { // ApiVersion runtime version @@ -85,6 +97,12 @@ type VersionResult struct { Version string `json:"version"` } +// LoginJSONBody defines parameters for Login. +type LoginJSONBody struct { + Password string `json:"password"` + Username string `json:"username"` +} + // DeleteObjectParams defines parameters for DeleteObject. type DeleteObjectParams struct { // Path relative to the ref @@ -130,18 +148,15 @@ type UploadObjectParams struct { IfNoneMatch *string `json:"If-None-Match,omitempty"` } -// LoginJSONBody defines parameters for Login. -type LoginJSONBody struct { - Password string `json:"password"` - Username string `json:"username"` -} +// LoginJSONRequestBody defines body for Login for application/json ContentType. +type LoginJSONRequestBody LoginJSONBody + +// RegisterJSONRequestBody defines body for Register for application/json ContentType. +type RegisterJSONRequestBody = UserRegisterInfo // UploadObjectMultipartRequestBody defines body for UploadObject for multipart/form-data ContentType. type UploadObjectMultipartRequestBody UploadObjectMultipartBody -// LoginJSONRequestBody defines body for Login for application/json ContentType. -type LoginJSONRequestBody LoginJSONBody - // RequestEditorFn is the function signature for the RequestEditor callback function type RequestEditorFn func(ctx context.Context, req *http.Request) error @@ -215,6 +230,16 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // LoginWithBody request with any body + LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RegisterWithBody request with any body + RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -227,17 +252,12 @@ type ClientInterface interface { // UploadObjectWithBody request with any body UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - // LoginWithBody request with any body - LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetVersion request GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) } -func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteObjectRequest(c.Server, repository, params) +func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -248,8 +268,8 @@ func (c *Client) DeleteObject(ctx context.Context, repository string, params *De return c.Client.Do(req) } -func (c *Client) GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetObjectRequest(c.Server, repository, params) +func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewLoginRequest(c.Server, body) if err != nil { return nil, err } @@ -260,8 +280,8 @@ func (c *Client) GetObject(ctx context.Context, repository string, params *GetOb return c.Client.Do(req) } -func (c *Client) HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewHeadObjectRequest(c.Server, repository, params) +func (c *Client) RegisterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequestWithBody(c.Server, contentType, body) if err != nil { return nil, err } @@ -272,8 +292,8 @@ func (c *Client) HeadObject(ctx context.Context, repository string, params *Head return c.Client.Do(req) } -func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewUploadObjectRequestWithBody(c.Server, repository, params, contentType, body) +func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRegisterRequest(c.Server, body) if err != nil { return nil, err } @@ -284,8 +304,8 @@ func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, pa return c.Client.Do(req) } -func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequestWithBody(c.Server, contentType, body) +func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteObjectRequest(c.Server, repository, params) if err != nil { return nil, err } @@ -296,8 +316,32 @@ func (c *Client) LoginWithBody(ctx context.Context, contentType string, body io. return c.Client.Do(req) } -func (c *Client) Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewLoginRequest(c.Server, body) +func (c *Client) GetObject(ctx context.Context, repository string, params *GetObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) HeadObject(ctx context.Context, repository string, params *HeadObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewHeadObjectRequest(c.Server, repository, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UploadObjectWithBody(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUploadObjectRequestWithBody(c.Server, repository, params, contentType, body) if err != nil { return nil, err } @@ -320,6 +364,86 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } +// NewLoginRequest calls the generic Login builder with application/json body +func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewLoginRequestWithBody(server, "application/json", bodyReader) +} + +// NewLoginRequestWithBody generates requests for Login with any type of body +func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/login") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewRegisterRequest calls the generic Register builder with application/json body +func NewRegisterRequest(server string, body RegisterJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewRegisterRequestWithBody(server, "application/json", bodyReader) +} + +// NewRegisterRequestWithBody generates requests for Register with any type of body +func NewRegisterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/register") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -607,46 +731,6 @@ func NewUploadObjectRequestWithBody(server string, repository string, params *Up return req, nil } -// NewLoginRequest calls the generic Login builder with application/json body -func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewLoginRequestWithBody(server, "application/json", bodyReader) -} - -// NewLoginRequestWithBody generates requests for Login with any type of body -func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/user/login") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - // NewGetVersionRequest generates requests for GetVersion func NewGetVersionRequest(server string) (*http.Request, error) { var err error @@ -717,6 +801,16 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { + // LoginWithBodyWithResponse request with any body + LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) + + // RegisterWithBodyWithResponse request with any body + RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + + RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -729,22 +823,18 @@ type ClientWithResponsesInterface interface { // UploadObjectWithBodyWithResponse request with any body UploadObjectWithBodyWithResponse(ctx context.Context, repository string, params *UploadObjectParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UploadObjectResponse, error) - // LoginWithBodyWithResponse request with any body - LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) - - LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) - // GetVersionWithResponse request GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) } -type DeleteObjectResponse struct { +type LoginResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r DeleteObjectResponse) Status() string { +func (r LoginResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -752,20 +842,21 @@ func (r DeleteObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteObjectResponse) StatusCode() int { +func (r LoginResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type GetObjectResponse struct { +type RegisterResponse struct { Body []byte HTTPResponse *http.Response + JSON201 *RegistrationMsg } // Status returns HTTPResponse.Status -func (r GetObjectResponse) Status() string { +func (r RegisterResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -773,20 +864,20 @@ func (r GetObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetObjectResponse) StatusCode() int { +func (r RegisterResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type HeadObjectResponse struct { +type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response } // Status returns HTTPResponse.Status -func (r HeadObjectResponse) Status() string { +func (r DeleteObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -794,21 +885,20 @@ func (r HeadObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r HeadObjectResponse) StatusCode() int { +func (r DeleteObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type UploadObjectResponse struct { +type GetObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *ObjectStats } // Status returns HTTPResponse.Status -func (r UploadObjectResponse) Status() string { +func (r GetObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -816,21 +906,20 @@ func (r UploadObjectResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r UploadObjectResponse) StatusCode() int { +func (r GetObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type LoginResponse struct { +type HeadObjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *AuthenticationToken } // Status returns HTTPResponse.Status -func (r LoginResponse) Status() string { +func (r HeadObjectResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -838,7 +927,29 @@ func (r LoginResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r LoginResponse) StatusCode() int { +func (r HeadObjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UploadObjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *ObjectStats +} + +// Status returns HTTPResponse.Status +func (r UploadObjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UploadObjectResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -867,6 +978,40 @@ func (r GetVersionResponse) StatusCode() int { return 0 } +// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse +func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { + rsp, err := c.Login(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseLoginResponse(rsp) +} + +// RegisterWithBodyWithResponse request with arbitrary body returning *RegisterResponse +func (c *ClientWithResponses) RegisterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.RegisterWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterResponse(rsp) +} + +func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) { + rsp, err := c.Register(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseRegisterResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, repository, params, reqEditors...) @@ -903,30 +1048,65 @@ func (c *ClientWithResponses) UploadObjectWithBodyWithResponse(ctx context.Conte return ParseUploadObjectResponse(rsp) } -// LoginWithBodyWithResponse request with arbitrary body returning *LoginResponse -func (c *ClientWithResponses) LoginWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.LoginWithBody(ctx, contentType, body, reqEditors...) +// GetVersionWithResponse request returning *GetVersionResponse +func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { + rsp, err := c.GetVersion(ctx, reqEditors...) if err != nil { return nil, err } - return ParseLoginResponse(rsp) + return ParseGetVersionResponse(rsp) } -func (c *ClientWithResponses) LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error) { - rsp, err := c.Login(ctx, body, reqEditors...) +// ParseLoginResponse parses an HTTP response from a LoginWithResponse call +func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseLoginResponse(rsp) + + response := &LoginResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AuthenticationToken + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil } -// GetVersionWithResponse request returning *GetVersionResponse -func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) { - rsp, err := c.GetVersion(ctx, reqEditors...) +// ParseRegisterResponse parses an HTTP response from a RegisterWithResponse call +func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - return ParseGetVersionResponse(rsp) + + response := &RegisterResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest RegistrationMsg + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil } // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call @@ -1003,32 +1183,6 @@ func ParseUploadObjectResponse(rsp *http.Response) (*UploadObjectResponse, error return response, nil } -// ParseLoginResponse parses an HTTP response from a LoginWithResponse call -func ParseLoginResponse(rsp *http.Response) (*LoginResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &LoginResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest AuthenticationToken - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - } - - return response, nil -} - // ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1057,6 +1211,12 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) { // ServerInterface represents all server handlers. type ServerInterface interface { + // perform a login + // (POST /auth/login) + Login(w *JiaozifsResponse, r *http.Request) + // perform user registration + // (POST /auth/register) + Register(w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) @@ -1069,9 +1229,6 @@ type ServerInterface interface { // (POST /repositories/{repository}/objects) UploadObject(w *JiaozifsResponse, r *http.Request, repository string, params UploadObjectParams) - // perform a login - // (POST /user/login) - Login(w *JiaozifsResponse, r *http.Request) // return program and runtime version // (GET /version) GetVersion(w *JiaozifsResponse, r *http.Request) @@ -1081,6 +1238,18 @@ type ServerInterface interface { type Unimplemented struct{} +// perform a login +// (POST /auth/login) +func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// perform user registration +// (POST /auth/register) +func (_ Unimplemented) Register(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) func (_ Unimplemented) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) { @@ -1104,12 +1273,6 @@ func (_ Unimplemented) UploadObject(w *JiaozifsResponse, r *http.Request, reposi w.WriteHeader(http.StatusNotImplemented) } -// perform a login -// (POST /user/login) -func (_ Unimplemented) Login(w *JiaozifsResponse, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - // return program and runtime version // (GET /version) func (_ Unimplemented) GetVersion(w *JiaozifsResponse, r *http.Request) { @@ -1125,6 +1288,36 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler +// Login operation middleware +func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Login(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// Register operation middleware +func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.Register(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteObject operation middleware func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1396,21 +1589,6 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r.WithContext(ctx)) } -// Login operation middleware -func (siw *ServerInterfaceWrapper) Login(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.Login(&JiaozifsResponse{w}, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - // GetVersion operation middleware func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1541,6 +1719,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl ErrorHandlerFunc: options.ErrorHandlerFunc, } + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/auth/login", wrapper.Login) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/auth/register", wrapper.Register) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/repositories/{repository}/objects", wrapper.DeleteObject) }) @@ -1553,9 +1737,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/repositories/{repository}/objects", wrapper.UploadObject) }) - r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/user/login", wrapper.Login) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/version", wrapper.GetVersion) }) @@ -1566,40 +1747,42 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 8d66d9a1..05bb2740 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -37,6 +37,75 @@ components: in: cookie name: saml_auth_session schemas: + UserUpdate: + type: object + required: + - username + - email + properties: + username: + type: string + email: + type: string + format: email + password: + type: string + minLength: 8 + + UserInfo: + type: object + required: + - username + - email + properties: + username: + type: string + email: + type: string + format: email + currentSignInAt: + type: string + format: date-time + lastSignInAt: + type: string + format: date-time + currentSignInIP: + type: string + format: ipv4 + lastSignInIP: + type: string + format: ipv4 + createdAt: + type: string + format: date-time + updateAt: + type: string + format: date-time + + RegistrationMsg: + type: object + required: + - message + properties: + message: + type: string + + UserRegisterInfo: + type: object + required: + - username + - email + - password + properties: + username: + type: string + password: + type: string + minLength: 8 + email: + type: string + format: email + AuthenticationToken: type: object required: @@ -49,6 +118,7 @@ components: type: integer format: int64 description: Unix Epoch in seconds + VersionResult: type: object required: @@ -395,7 +465,7 @@ paths: /auth/login: post: tags: - - user + - auth operationId: login summary: perform a login security: [] # No authentication @@ -430,3 +500,29 @@ paths: description: too many requests default: description: Internal Server Error + + /auth/register: + post: + tags: + - auth + operationId: register + summary: perform user registration + security: [] # No authentication + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UserRegisterInfo" + responses: + 201: + description: registration success message + content: + application/json: + schema: + $ref: "#/components/schemas/RegistrationMsg" + 400: + description: Bad Request - Validation Error + 420: + description: too many requests + default: + description: Internal Server Error diff --git a/auth/basic_auth.go b/auth/basic_auth.go index b5f83089..99f77aad 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -18,6 +18,11 @@ type Login struct { Username string `json:"username"` Password string `json:"password"` } +type Register struct { + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` +} func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { ctx := context.Background() @@ -50,3 +55,39 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a return token, nil } + +func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { + ctx := context.Background() + // check username, email + if userRepo.CheckUserByNameEmail(ctx, l.Username, l.Email) { + msg.Message = "The username or email has already been registered" + return + } + + password, err := bcrypt.GenerateFromPassword([]byte(l.Password), passwordCost) + if err != nil { + msg.Message = "Generate Password err" + return + } + + // insert db + user := &models.User{ + Name: l.Username, + Email: l.Email, + EncryptedPassword: string(password), + CurrentSignInAt: time.Time{}, + LastSignInAt: time.Time{}, + CurrentSignInIP: "", + LastSignInIP: "", + CreatedAt: time.Now(), + UpdatedAt: time.Time{}, + } + insertUser, err := userRepo.Insert(ctx, user) + if err != nil { + msg.Message = "register user err" + return + } + // return + msg.Message = insertUser.Name + " register success" + return msg, nil +} diff --git a/auth/types.go b/auth/types.go index aaea0caf..badea89f 100644 --- a/auth/types.go +++ b/auth/types.go @@ -4,4 +4,5 @@ import "time" const ( expirationDuration = time.Hour + passwordCost = 12 ) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index f96af687..02be39b3 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -37,3 +37,20 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { // resp w.RespJSON(resp) } + +func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { + // Decode requestBody + var register auth.Register + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(®ister); err != nil { + w.RespError(err) + } + // Perform register + msg, err := register.Register(*A.UserRepo) + if err != nil { + w.RespError(err) + return + } + // resp + w.RespJSON(msg) +} diff --git a/models/user.go b/models/user.go index b6132f65..2b9c87ec 100644 --- a/models/user.go +++ b/models/user.go @@ -28,6 +28,7 @@ type IUserRepo interface { Insert(ctx context.Context, user *User) (*User, error) GetEPByName(ctx context.Context, name string) (string, error) + CheckUserByNameEmail(ctx context.Context, name, email string) bool } var _ IUserRepo = (*UserRepo)(nil) @@ -59,5 +60,13 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, Model((*User)(nil)).Column("encrypted_password"). Where("name = ?", name). Scan(ctx, &ep) +} +func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { + err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). + Limit(1).Scan(ctx) + if err != nil { + return false + } + return true } From 24a98eb416737cc8832ff63f36afcab45f31dae7 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 17:50:47 +0800 Subject: [PATCH 06/20] feat: add user profile --- api/jiaozifs.gen.go | 218 ++++++++++++++++++++++++++++++++++------- api/swagger.yml | 20 ++++ auth/basic_auth.go | 55 ++++++++++- auth/jwt_login.go | 12 +-- controller/user_ctl.go | 15 +++ models/user.go | 6 ++ 6 files changed, 279 insertions(+), 47 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index 65344397..cde0ac77 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -15,6 +15,7 @@ import ( "net/url" "path" "strings" + "time" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" @@ -81,6 +82,18 @@ type RegistrationMsg struct { Message string `json:"message"` } +// UserInfo defines model for UserInfo. +type UserInfo struct { + CreatedAt *time.Time `json:"createdAt,omitempty"` + CurrentSignInAt *time.Time `json:"currentSignInAt,omitempty"` + CurrentSignInIP *string `json:"currentSignInIP,omitempty"` + Email openapi_types.Email `json:"email"` + LastSignInAt *time.Time `json:"lastSignInAt,omitempty"` + LastSignInIP *string `json:"lastSignInIP,omitempty"` + UpdateAt *time.Time `json:"updateAt,omitempty"` + Username string `json:"username"` +} + // UserRegisterInfo defines model for UserRegisterInfo. type UserRegisterInfo struct { Email openapi_types.Email `json:"email"` @@ -240,6 +253,9 @@ type ClientInterface interface { Register(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetUserInfo request + GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteObject request DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -304,6 +320,18 @@ func (c *Client) Register(ctx context.Context, body RegisterJSONRequestBody, req return c.Client.Do(req) } +func (c *Client) GetUserInfo(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetUserInfoRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) DeleteObject(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewDeleteObjectRequest(c.Server, repository, params) if err != nil { @@ -444,6 +472,33 @@ func NewRegisterRequestWithBody(server string, contentType string, body io.Reade return req, nil } +// NewGetUserInfoRequest generates requests for GetUserInfo +func NewGetUserInfoRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/auth/user") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteObjectRequest generates requests for DeleteObject func NewDeleteObjectRequest(server string, repository string, params *DeleteObjectParams) (*http.Request, error) { var err error @@ -811,6 +866,9 @@ type ClientWithResponsesInterface interface { RegisterWithResponse(ctx context.Context, body RegisterJSONRequestBody, reqEditors ...RequestEditorFn) (*RegisterResponse, error) + // GetUserInfoWithResponse request + GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) + // DeleteObjectWithResponse request DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) @@ -871,6 +929,28 @@ func (r RegisterResponse) StatusCode() int { return 0 } +type GetUserInfoResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *UserInfo +} + +// Status returns HTTPResponse.Status +func (r GetUserInfoResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetUserInfoResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type DeleteObjectResponse struct { Body []byte HTTPResponse *http.Response @@ -1012,6 +1092,15 @@ func (c *ClientWithResponses) RegisterWithResponse(ctx context.Context, body Reg return ParseRegisterResponse(rsp) } +// GetUserInfoWithResponse request returning *GetUserInfoResponse +func (c *ClientWithResponses) GetUserInfoWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetUserInfoResponse, error) { + rsp, err := c.GetUserInfo(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetUserInfoResponse(rsp) +} + // DeleteObjectWithResponse request returning *DeleteObjectResponse func (c *ClientWithResponses) DeleteObjectWithResponse(ctx context.Context, repository string, params *DeleteObjectParams, reqEditors ...RequestEditorFn) (*DeleteObjectResponse, error) { rsp, err := c.DeleteObject(ctx, repository, params, reqEditors...) @@ -1109,6 +1198,32 @@ func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { return response, nil } +// ParseGetUserInfoResponse parses an HTTP response from a GetUserInfoWithResponse call +func ParseGetUserInfoResponse(rsp *http.Response) (*GetUserInfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetUserInfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UserInfo + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseDeleteObjectResponse parses an HTTP response from a DeleteObjectWithResponse call func ParseDeleteObjectResponse(rsp *http.Response) (*DeleteObjectResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -1217,6 +1332,9 @@ type ServerInterface interface { // perform user registration // (POST /auth/register) Register(w *JiaozifsResponse, r *http.Request) + // get information of the currently logged-in user + // (GET /auth/user) + GetUserInfo(w *JiaozifsResponse, r *http.Request) // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) @@ -1250,6 +1368,12 @@ func (_ Unimplemented) Register(w *JiaozifsResponse, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// get information of the currently logged-in user +// (GET /auth/user) +func (_ Unimplemented) GetUserInfo(w *JiaozifsResponse, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // delete object. Missing objects will not return a NotFound error. // (DELETE /repositories/{repository}/objects) func (_ Unimplemented) DeleteObject(w *JiaozifsResponse, r *http.Request, repository string, params DeleteObjectParams) { @@ -1318,6 +1442,23 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r.WithContext(ctx)) } +// GetUserInfo operation middleware +func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetUserInfo(&JiaozifsResponse{w}, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // DeleteObject operation middleware func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1725,6 +1866,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/auth/register", wrapper.Register) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/auth/user", wrapper.GetUserInfo) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/repositories/{repository}/objects", wrapper.DeleteObject) }) @@ -1747,42 +1891,44 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yml b/api/swagger.yml index 05bb2740..42380df8 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -526,3 +526,23 @@ paths: description: too many requests default: description: Internal Server Error + + /auth/user: + get: + tags: + - auth + operationId: getUserInfo + summary: get information of the currently logged-in user + security: + - jwt_token: ["aaaa"] + responses: + 200: + description: Successful get of user Info + content: + application/json: + schema: + $ref: "#/components/schemas/UserInfo" + 401: + description: Unauthorized + default: + description: Internal Server Error diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 99f77aad..ffa37ea2 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,6 +2,9 @@ package auth import ( "context" + "errors" + "github.com/golang-jwt/jwt" + openapi_types "github.com/oapi-codegen/runtime/types" "time" "github.com/go-openapi/swag" @@ -23,6 +26,9 @@ type Register struct { Email string `json:"email"` Password string `json:"password"` } +type UserInfo struct { + Token string `json:"token"` +} func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { ctx := context.Background() @@ -56,15 +62,15 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a return token, nil } -func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { +func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { ctx := context.Background() // check username, email - if userRepo.CheckUserByNameEmail(ctx, l.Username, l.Email) { + if userRepo.CheckUserByNameEmail(ctx, r.Username, r.Email) { msg.Message = "The username or email has already been registered" return } - password, err := bcrypt.GenerateFromPassword([]byte(l.Password), passwordCost) + password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { msg.Message = "Generate Password err" return @@ -72,8 +78,8 @@ func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, // insert db user := &models.User{ - Name: l.Username, - Email: l.Email, + Name: r.Username, + Email: r.Email, EncryptedPassword: string(password), CurrentSignInAt: time.Time{}, LastSignInAt: time.Time{}, @@ -91,3 +97,42 @@ func (l *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, msg.Message = insertUser.Name + " register success" return msg, nil } + +func (u *UserInfo) UserProfile(userRepo models.IUserRepo, config *config.Config) (api.UserInfo, error) { + ctx := context.Background() + userInfo := api.UserInfo{} + // Parse JWT Token + token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { + return config.Auth.SecretKey, nil + }) + if err != nil { + return userInfo, err + } + // Check Token validity + if !token.Valid { + return userInfo, errors.New("token is invalid") + } + // Get username by token + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return userInfo, errors.New("failed to extract claims from JWT token") + } + username := claims["sub"].(string) + + // Get user by username + user, err := userRepo.GetUserByName(ctx, username) + if err != nil { + return userInfo, err + } + userInfo = api.UserInfo{ + CreatedAt: &user.CreatedAt, + CurrentSignInAt: &user.CurrentSignInAt, + CurrentSignInIP: &user.CurrentSignInIP, + Email: openapi_types.Email(user.Email), + LastSignInAt: &user.LastSignInAt, + LastSignInIP: &user.LastSignInIP, + UpdateAt: &user.UpdatedAt, + Username: user.Name, + } + return userInfo, nil +} diff --git a/auth/jwt_login.go b/auth/jwt_login.go index e1373f6a..0dbd533f 100644 --- a/auth/jwt_login.go +++ b/auth/jwt_login.go @@ -15,12 +15,12 @@ const ( // It supports backward compatibility for creating a login jwt. The audience is not set for login token. Any audience will make the token // invalid for login. No email is passed to support the ability of login for users via user/access keys which don't have an email yet func GenerateJWTLogin(secret []byte, userID string, issuedAt, expiresAt time.Time) (string, error) { - claims := &jwt.StandardClaims{ - Id: uuid.NewString(), - Audience: LoginAudience, - Subject: userID, - IssuedAt: issuedAt.Unix(), - ExpiresAt: expiresAt.Unix(), + claims := jwt.MapClaims{ + "id": uuid.NewString(), + "aud": LoginAudience, + "sub": userID, + "iat": issuedAt.Unix(), + "exp": expiresAt.Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(secret) diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 02be39b3..87fb7c39 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -54,3 +54,18 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { // resp w.RespJSON(msg) } + +func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { + // Get token from Header + tokenString := r.Header.Get("Authorization") + userInfo := &auth.UserInfo{Token: tokenString} + + // Perform GetUserInfo + info, err := userInfo.UserProfile(*A.UserRepo, A.Config) + if err != nil { + w.RespError(err) + return + } + // resp + w.RespJSON(info) +} diff --git a/models/user.go b/models/user.go index 2b9c87ec..8d640f46 100644 --- a/models/user.go +++ b/models/user.go @@ -28,6 +28,7 @@ type IUserRepo interface { Insert(ctx context.Context, user *User) (*User, error) GetEPByName(ctx context.Context, name string) (string, error) + GetUserByName(ctx context.Context, name string) (*User, error) CheckUserByNameEmail(ctx context.Context, name, email string) bool } @@ -70,3 +71,8 @@ func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email } return true } + +func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { + user := &User{} + return user, userRepo.DB.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) +} From ae162b053ff201f7aef233af7cd2f48a877e7c18 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 18:17:10 +0800 Subject: [PATCH 07/20] chore: add test, reformat code --- auth/basic_auth.go | 3 ++- models/user.go | 5 +---- models/user_test.go | 6 ++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index ffa37ea2..729b0acd 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -3,9 +3,10 @@ package auth import ( "context" "errors" + "time" + "github.com/golang-jwt/jwt" openapi_types "github.com/oapi-codegen/runtime/types" - "time" "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" diff --git a/models/user.go b/models/user.go index 8d640f46..531fa7ef 100644 --- a/models/user.go +++ b/models/user.go @@ -66,10 +66,7 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). Limit(1).Scan(ctx) - if err != nil { - return false - } - return true + return err != nil } func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { diff --git a/models/user_test.go b/models/user_test.go index c0a6abd9..e6c5aa16 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -67,4 +67,10 @@ func TestNewUserRepo(t *testing.T) { ep, err := repo.GetEPByName(ctx, newUser.Name) require.NoError(t, err) require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) + + b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) + require.True(t, !b) + + user, err = repo.GetUserByName(ctx, newUser.Name) + require.NoError(t, err) } From 61b356b7d3307682d89f0548f4f8cfd2005fed31 Mon Sep 17 00:00:00 2001 From: zjy Date: Mon, 4 Dec 2023 18:24:17 +0800 Subject: [PATCH 08/20] fix: fix judge conditions --- models/user_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models/user_test.go b/models/user_test.go index e6c5aa16..e4388c15 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -69,8 +69,9 @@ func TestNewUserRepo(t *testing.T) { require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) - require.True(t, !b) + require.True(t, b) - user, err = repo.GetUserByName(ctx, newUser.Name) + u, err := repo.GetUserByName(ctx, newUser.Name) require.NoError(t, err) + require.True(t, cmp.Equal(userModel.UpdatedAt, u.UpdatedAt, dbTimeCmpOpt)) } From 20e7a924f21ab884cab291a5c7da2a7f935487ff Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 15:54:05 +0800 Subject: [PATCH 09/20] refactor: add service interface based on the original code. --- auth/basic_auth.go | 40 ++++++++++++++--------------- auth/service.go | 57 ++++++++++++++++++++++++++++++++++++++++++ cmd/daemon.go | 3 +++ controller/user_ctl.go | 14 +++++------ models/user.go | 2 +- models/user_test.go | 2 +- 6 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 auth/service.go diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 729b0acd..64487b05 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -11,7 +11,6 @@ import ( "github.com/go-openapi/swag" logging "github.com/ipfs/go-log/v2" "github.com/jiaozifs/jiaozifs/api" - "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" "golang.org/x/crypto/bcrypt" ) @@ -22,19 +21,10 @@ type Login struct { Username string `json:"username"` Password string `json:"password"` } -type Register struct { - Username string `json:"username"` - Email string `json:"email"` - Password string `json:"password"` -} -type UserInfo struct { - Token string `json:"token"` -} -func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { - ctx := context.Background() +func (l *Login) Login(ctx context.Context, authService Service) (token api.AuthenticationToken, err error) { // Get user encryptedPassword by username - ep, err := userRepo.GetEPByName(ctx, l.Username) + ep, err := authService.GetEPByName(ctx, l.Username) if err != nil { log.Errorf("username err: %s", err) return token, err @@ -49,7 +39,7 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a // Generate user token loginTime := time.Now() expires := loginTime.Add(expirationDuration) - secretKey := config.Auth.SecretKey + secretKey := authService.GetSecretKey() tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) if err != nil { @@ -63,10 +53,15 @@ func (l *Login) Login(userRepo models.IUserRepo, config *config.Config) (token a return token, nil } -func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, err error) { - ctx := context.Background() +type Register struct { + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` +} + +func (r *Register) Register(ctx context.Context, authService Service) (msg api.RegistrationMsg, err error) { // check username, email - if userRepo.CheckUserByNameEmail(ctx, r.Username, r.Email) { + if authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { msg.Message = "The username or email has already been registered" return } @@ -89,7 +84,7 @@ func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, CreatedAt: time.Now(), UpdatedAt: time.Time{}, } - insertUser, err := userRepo.Insert(ctx, user) + insertUser, err := authService.Insert(ctx, user) if err != nil { msg.Message = "register user err" return @@ -99,12 +94,15 @@ func (r *Register) Register(userRepo models.IUserRepo) (msg api.RegistrationMsg, return msg, nil } -func (u *UserInfo) UserProfile(userRepo models.IUserRepo, config *config.Config) (api.UserInfo, error) { - ctx := context.Background() +type UserInfo struct { + Token string `json:"token"` +} + +func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.UserInfo, error) { userInfo := api.UserInfo{} // Parse JWT Token token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { - return config.Auth.SecretKey, nil + return authService.GetSecretKey(), nil }) if err != nil { return userInfo, err @@ -121,7 +119,7 @@ func (u *UserInfo) UserProfile(userRepo models.IUserRepo, config *config.Config) username := claims["sub"].(string) // Get user by username - user, err := userRepo.GetUserByName(ctx, username) + user, err := authService.GetUserByName(ctx, username) if err != nil { return userInfo, err } diff --git a/auth/service.go b/auth/service.go new file mode 100644 index 00000000..757d062e --- /dev/null +++ b/auth/service.go @@ -0,0 +1,57 @@ +package auth + +import ( + "context" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" +) + +type Service interface { + // user + Insert(ctx context.Context, user *models.User) (*models.User, error) + GetEPByName(ctx context.Context, name string) (string, error) + GetUserByName(ctx context.Context, name string) (*models.User, error) + CheckUserByNameEmail(ctx context.Context, name, email string) bool + // config + GetSecretKey() []byte +} + +var _ Service = (*AuthService)(nil) + +type AuthService struct { + UserRepo *models.IUserRepo + Config *config.Config +} + +func (a AuthService) GetUserByName(ctx context.Context, name string) (*models.User, error) { + return (*a.UserRepo).GetUserByName(ctx, name) +} + +func (a AuthService) Insert(ctx context.Context, user *models.User) (*models.User, error) { + return (*a.UserRepo).Insert(ctx, user) +} + +func (a AuthService) CheckUserByNameEmail(ctx context.Context, name, email string) bool { + return (*a.UserRepo).CheckUserByNameEmail(ctx, name, email) +} + +func (a AuthService) GetSecretKey() []byte { + return (*a.Config).Auth.SecretKey +} + +func (a AuthService) GetEPByName(ctx context.Context, name string) (string, error) { + ep, err := (*a.UserRepo).GetEPByName(ctx, name) + if err != nil { + return "", err + } + return ep, nil +} + +func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *AuthService { + log.Info("initialized Auth service") + res := &AuthService{ + UserRepo: userRepo, + Config: config, + } + return res +} diff --git a/cmd/daemon.go b/cmd/daemon.go index cd5e8038..cbbcd83a 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/block/params" @@ -47,6 +48,8 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.APIConfig), &cfg.API), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), + //auth + fx_opt.Override(new(auth.Service), auth.NewAuthService), //blockstore fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 87fb7c39..a9964f74 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -6,19 +6,17 @@ import ( "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" "go.uber.org/fx" ) type UserController struct { fx.In - UserRepo *models.IUserRepo - Config *config.Config + Auth auth.Service } func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { + ctx := r.Context() // Decode requestBody var login auth.Login decoder := json.NewDecoder(r.Body) @@ -28,7 +26,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } // Perform login - resp, err := login.Login(*A.UserRepo, A.Config) + resp, err := login.Login(ctx, A.Auth) if err != nil { w.RespError(err) return @@ -39,6 +37,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { + ctx := r.Context() // Decode requestBody var register auth.Register decoder := json.NewDecoder(r.Body) @@ -46,7 +45,7 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { w.RespError(err) } // Perform register - msg, err := register.Register(*A.UserRepo) + msg, err := register.Register(ctx, A.Auth) if err != nil { w.RespError(err) return @@ -56,12 +55,13 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { } func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { + ctx := r.Context() // Get token from Header tokenString := r.Header.Get("Authorization") userInfo := &auth.UserInfo{Token: tokenString} // Perform GetUserInfo - info, err := userInfo.UserProfile(*A.UserRepo, A.Config) + info, err := userInfo.UserProfile(ctx, A.Auth) if err != nil { w.RespError(err) return diff --git a/models/user.go b/models/user.go index 531fa7ef..87c2bd3e 100644 --- a/models/user.go +++ b/models/user.go @@ -66,7 +66,7 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). Limit(1).Scan(ctx) - return err != nil + return err == nil } func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { diff --git a/models/user_test.go b/models/user_test.go index e4388c15..780ef9a6 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -69,7 +69,7 @@ func TestNewUserRepo(t *testing.T) { require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) - require.True(t, b) + require.True(t, !b) u, err := repo.GetUserByName(ctx, newUser.Name) require.NoError(t, err) From a3bb3b6800b9e5d4edec587a43e13948b051238d Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 16:06:53 +0800 Subject: [PATCH 10/20] chore: goimports code --- auth/service.go | 1 + cmd/daemon.go | 1 + 2 files changed, 2 insertions(+) diff --git a/auth/service.go b/auth/service.go index 757d062e..80c9b71b 100644 --- a/auth/service.go +++ b/auth/service.go @@ -2,6 +2,7 @@ package auth import ( "context" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" ) diff --git a/cmd/daemon.go b/cmd/daemon.go index cbbcd83a..bd32bd99 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "github.com/jiaozifs/jiaozifs/auth" "github.com/jiaozifs/jiaozifs/block/params" From 433492f7787010f574165ba8eefe494a15aebd67 Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 16:22:38 +0800 Subject: [PATCH 11/20] chore: update Service impl name --- auth/service.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/auth/service.go b/auth/service.go index 80c9b71b..d06e4b31 100644 --- a/auth/service.go +++ b/auth/service.go @@ -17,30 +17,30 @@ type Service interface { GetSecretKey() []byte } -var _ Service = (*AuthService)(nil) +var _ Service = (*ServiceAuth)(nil) -type AuthService struct { +type ServiceAuth struct { UserRepo *models.IUserRepo Config *config.Config } -func (a AuthService) GetUserByName(ctx context.Context, name string) (*models.User, error) { +func (a ServiceAuth) GetUserByName(ctx context.Context, name string) (*models.User, error) { return (*a.UserRepo).GetUserByName(ctx, name) } -func (a AuthService) Insert(ctx context.Context, user *models.User) (*models.User, error) { +func (a ServiceAuth) Insert(ctx context.Context, user *models.User) (*models.User, error) { return (*a.UserRepo).Insert(ctx, user) } -func (a AuthService) CheckUserByNameEmail(ctx context.Context, name, email string) bool { +func (a ServiceAuth) CheckUserByNameEmail(ctx context.Context, name, email string) bool { return (*a.UserRepo).CheckUserByNameEmail(ctx, name, email) } -func (a AuthService) GetSecretKey() []byte { +func (a ServiceAuth) GetSecretKey() []byte { return (*a.Config).Auth.SecretKey } -func (a AuthService) GetEPByName(ctx context.Context, name string) (string, error) { +func (a ServiceAuth) GetEPByName(ctx context.Context, name string) (string, error) { ep, err := (*a.UserRepo).GetEPByName(ctx, name) if err != nil { return "", err @@ -48,9 +48,9 @@ func (a AuthService) GetEPByName(ctx context.Context, name string) (string, erro return ep, nil } -func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *AuthService { +func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *ServiceAuth { log.Info("initialized Auth service") - res := &AuthService{ + res := &ServiceAuth{ UserRepo: userRepo, Config: config, } From 63e606948738749dd55bfa66ba0491d200c25852 Mon Sep 17 00:00:00 2001 From: zjy Date: Tue, 5 Dec 2023 16:49:09 +0800 Subject: [PATCH 12/20] feat: add some log info --- auth/basic_auth.go | 19 ++++++++++++++----- auth/errors.go | 11 +++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 auth/errors.go diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 64487b05..1dcedb64 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,7 +2,6 @@ package auth import ( "context" - "errors" "time" "github.com/golang-jwt/jwt" @@ -47,6 +46,8 @@ func (l *Login) Login(ctx context.Context, authService Service) (token api.Authe return token, err } + log.Info("login successful") + token.Token = tokenString token.TokenExpiration = swag.Int64(expires.Unix()) @@ -61,14 +62,16 @@ type Register struct { func (r *Register) Register(ctx context.Context, authService Service) (msg api.RegistrationMsg, err error) { // check username, email - if authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { + if !authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { msg.Message = "The username or email has already been registered" + log.Error(ErrInvalidNameEmail) return } password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { - msg.Message = "Generate Password err" + msg.Message = "compare password err" + log.Error(ErrComparePassword) return } @@ -87,9 +90,11 @@ func (r *Register) Register(ctx context.Context, authService Service) (msg api.R insertUser, err := authService.Insert(ctx, user) if err != nil { msg.Message = "register user err" + log.Error(msg.Message) return } // return + log.Info("registration success") msg.Message = insertUser.Name + " register success" return msg, nil } @@ -105,16 +110,19 @@ func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.Us return authService.GetSecretKey(), nil }) if err != nil { + log.Error(ErrParseToken) return userInfo, err } // Check Token validity if !token.Valid { - return userInfo, errors.New("token is invalid") + log.Error(ErrInvalidToken) + return userInfo, ErrInvalidToken } // Get username by token claims, ok := token.Claims.(jwt.MapClaims) if !ok { - return userInfo, errors.New("failed to extract claims from JWT token") + log.Error(ErrExtractClaims) + return userInfo, ErrExtractClaims } username := claims["sub"].(string) @@ -133,5 +141,6 @@ func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.Us UpdateAt: &user.UpdatedAt, Username: user.Name, } + log.Info("get user profile success") return userInfo, nil } diff --git a/auth/errors.go b/auth/errors.go new file mode 100644 index 00000000..3e824527 --- /dev/null +++ b/auth/errors.go @@ -0,0 +1,11 @@ +package auth + +import "errors" + +var ( + ErrComparePassword = errors.New("compare password error") + ErrParseToken = errors.New("parse token error") + ErrInvalidToken = errors.New("invalid token") + ErrInvalidNameEmail = errors.New("invalid name or email") + ErrExtractClaims = errors.New("failed to extract claims from JWT token") +) From fe9da01f123ddf4d840d27575efbb51ae0622d78 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:48:05 +0800 Subject: [PATCH 13/20] fix: remove redundant parameters and update rep code --- api/swagger.yml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/api/swagger.yml b/api/swagger.yml index 42380df8..5fab774d 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -13,8 +13,8 @@ servers: description: jiaozifs server endpoint security: - - jwt_token: ["aaaa"] - - basic_auth: ["aaaaa"] + - jwt_token: [] + - basic_auth: [] components: securitySchemes: basic_auth: @@ -82,14 +82,6 @@ components: type: string format: date-time - RegistrationMsg: - type: object - required: - - message - properties: - message: - type: string - UserRegisterInfo: type: object required: @@ -515,11 +507,7 @@ paths: $ref: "#/components/schemas/UserRegisterInfo" responses: 201: - description: registration success message - content: - application/json: - schema: - $ref: "#/components/schemas/RegistrationMsg" + description: registration success 400: description: Bad Request - Validation Error 420: @@ -534,7 +522,7 @@ paths: operationId: getUserInfo summary: get information of the currently logged-in user security: - - jwt_token: ["aaaa"] + - jwt_token: [] responses: 200: description: Successful get of user Info From 24fdd643078eef4bc64a2805c0d8c135a054f3f3 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:49:10 +0800 Subject: [PATCH 14/20] test: add auth test --- auth/auth_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 auth/auth_test.go diff --git a/auth/auth_test.go b/auth/auth_test.go new file mode 100644 index 00000000..8ee1aad3 --- /dev/null +++ b/auth/auth_test.go @@ -0,0 +1,70 @@ +package auth + +import ( + "context" + "fmt" + "github.com/brianvoe/gofakeit/v6" + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" + "github.com/jiaozifs/jiaozifs/models/migrations" + "github.com/phayes/freeport" + "github.com/stretchr/testify/require" + "github.com/uptrace/bun" + "go.uber.org/fx/fxtest" + "testing" +) + +var testConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" + +func setup(ctx context.Context, t *testing.T) (*embeddedpostgres.EmbeddedPostgres, *bun.DB) { + port, err := freeport.GetFreePort() + require.NoError(t, err) + postgres := embeddedpostgres.NewDatabase(embeddedpostgres.DefaultConfig().Port(uint32(port)).Database("jiaozifs")) + err = postgres.Start() + require.NoError(t, err) + + db, err := models.SetupDatabase(ctx, fxtest.NewLifecycle(t), &config.DatabaseConfig{Debug: true, Connection: fmt.Sprintf(testConnTmpl, port)}) + require.NoError(t, err) + + err = migrations.MigrateDatabase(ctx, db) + require.NoError(t, err) + return postgres, db +} + +func TestLogin_Success(t *testing.T) { + ctx := context.Background() + postgres, db := setup(ctx, t) + defer postgres.Stop() //nolint + // repo + mockRepo := models.NewUserRepo(db) + // config + mockConfig := &config.Config{Auth: config.AuthConfig{SecretKey: []byte("THIS_MUST_BE_CHANGED_IN_PRODUCTION")}} + // user + userModel := &models.User{} + require.NoError(t, gofakeit.Struct(userModel)) + + // registration + register := &Register{ + Username: userModel.Name, + Email: userModel.Email, + Password: userModel.EncryptedPassword, + } + err := register.Register(ctx, mockRepo) + require.NoError(t, err) + + // login + login := &Login{ + Username: userModel.Name, + Password: userModel.EncryptedPassword, + } + token, err := login.Login(context.Background(), mockRepo, mockConfig) + require.NoError(t, err, "Login should not return an error") + require.NotEmpty(t, token.Token, "Token should not be empty") + require.NotNil(t, token.TokenExpiration, "Token expiration should not be nil") + // profile + userInfo := &UserInfo{Token: token.Token} + profile, err := userInfo.UserProfile(ctx, mockRepo, mockConfig) + require.NoError(t, err) + require.NotEmpty(t, profile) +} From e7fd8f8f14627dd2c3a6714e634306272cb6892c Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:55:16 +0800 Subject: [PATCH 15/20] refactor: remove auth interface --- auth/basic_auth.go | 32 +++++++++++------------ auth/service.go | 58 ------------------------------------------ controller/user_ctl.go | 13 ++++++---- 3 files changed, 24 insertions(+), 79 deletions(-) delete mode 100644 auth/service.go diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 1dcedb64..8fb8a641 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,6 +2,7 @@ package auth import ( "context" + "github.com/jiaozifs/jiaozifs/config" "time" "github.com/golang-jwt/jwt" @@ -21,9 +22,9 @@ type Login struct { Password string `json:"password"` } -func (l *Login) Login(ctx context.Context, authService Service) (token api.AuthenticationToken, err error) { +func (l *Login) Login(ctx context.Context, repo models.IUserRepo, config *config.Config) (token api.AuthenticationToken, err error) { // Get user encryptedPassword by username - ep, err := authService.GetEPByName(ctx, l.Username) + ep, err := repo.GetEPByName(ctx, l.Username) if err != nil { log.Errorf("username err: %s", err) return token, err @@ -38,7 +39,7 @@ func (l *Login) Login(ctx context.Context, authService Service) (token api.Authe // Generate user token loginTime := time.Now() expires := loginTime.Add(expirationDuration) - secretKey := authService.GetSecretKey() + secretKey := config.Auth.SecretKey tokenString, err := GenerateJWTLogin(secretKey, l.Username, loginTime, expires) if err != nil { @@ -60,17 +61,18 @@ type Register struct { Password string `json:"password"` } -func (r *Register) Register(ctx context.Context, authService Service) (msg api.RegistrationMsg, err error) { +func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err error) { // check username, email - if !authService.CheckUserByNameEmail(ctx, r.Username, r.Email) { - msg.Message = "The username or email has already been registered" + _, err1 := repo.GetUserByName(ctx, r.Username) + _, err2 := repo.GetUserByEmail(ctx, r.Email) + if err1 == nil || err2 == nil { + err = ErrInvalidNameEmail log.Error(ErrInvalidNameEmail) return } password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { - msg.Message = "compare password err" log.Error(ErrComparePassword) return } @@ -87,27 +89,25 @@ func (r *Register) Register(ctx context.Context, authService Service) (msg api.R CreatedAt: time.Now(), UpdatedAt: time.Time{}, } - insertUser, err := authService.Insert(ctx, user) + insertUser, err := repo.Insert(ctx, user) if err != nil { - msg.Message = "register user err" - log.Error(msg.Message) + log.Error("create user error") return } // return - log.Info("registration success") - msg.Message = insertUser.Name + " register success" - return msg, nil + log.Infof("%s registration success", insertUser.Name) + return nil } type UserInfo struct { Token string `json:"token"` } -func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.UserInfo, error) { +func (u *UserInfo) UserProfile(ctx context.Context, repo models.IUserRepo, config *config.Config) (api.UserInfo, error) { userInfo := api.UserInfo{} // Parse JWT Token token, err := jwt.Parse(u.Token, func(token *jwt.Token) (interface{}, error) { - return authService.GetSecretKey(), nil + return config.Auth.SecretKey, nil }) if err != nil { log.Error(ErrParseToken) @@ -127,7 +127,7 @@ func (u *UserInfo) UserProfile(ctx context.Context, authService Service) (api.Us username := claims["sub"].(string) // Get user by username - user, err := authService.GetUserByName(ctx, username) + user, err := repo.GetUserByName(ctx, username) if err != nil { return userInfo, err } diff --git a/auth/service.go b/auth/service.go deleted file mode 100644 index d06e4b31..00000000 --- a/auth/service.go +++ /dev/null @@ -1,58 +0,0 @@ -package auth - -import ( - "context" - - "github.com/jiaozifs/jiaozifs/config" - "github.com/jiaozifs/jiaozifs/models" -) - -type Service interface { - // user - Insert(ctx context.Context, user *models.User) (*models.User, error) - GetEPByName(ctx context.Context, name string) (string, error) - GetUserByName(ctx context.Context, name string) (*models.User, error) - CheckUserByNameEmail(ctx context.Context, name, email string) bool - // config - GetSecretKey() []byte -} - -var _ Service = (*ServiceAuth)(nil) - -type ServiceAuth struct { - UserRepo *models.IUserRepo - Config *config.Config -} - -func (a ServiceAuth) GetUserByName(ctx context.Context, name string) (*models.User, error) { - return (*a.UserRepo).GetUserByName(ctx, name) -} - -func (a ServiceAuth) Insert(ctx context.Context, user *models.User) (*models.User, error) { - return (*a.UserRepo).Insert(ctx, user) -} - -func (a ServiceAuth) CheckUserByNameEmail(ctx context.Context, name, email string) bool { - return (*a.UserRepo).CheckUserByNameEmail(ctx, name, email) -} - -func (a ServiceAuth) GetSecretKey() []byte { - return (*a.Config).Auth.SecretKey -} - -func (a ServiceAuth) GetEPByName(ctx context.Context, name string) (string, error) { - ep, err := (*a.UserRepo).GetEPByName(ctx, name) - if err != nil { - return "", err - } - return ep, nil -} - -func NewAuthService(userRepo *models.IUserRepo, config *config.Config) *ServiceAuth { - log.Info("initialized Auth service") - res := &ServiceAuth{ - UserRepo: userRepo, - Config: config, - } - return res -} diff --git a/controller/user_ctl.go b/controller/user_ctl.go index a9964f74..1b159db8 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,6 +2,8 @@ package controller import ( "encoding/json" + "github.com/jiaozifs/jiaozifs/config" + "github.com/jiaozifs/jiaozifs/models" "net/http" "github.com/jiaozifs/jiaozifs/api" @@ -12,7 +14,8 @@ import ( type UserController struct { fx.In - Auth auth.Service + Repo models.IUserRepo + Config *config.Config } func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { @@ -26,7 +29,7 @@ func (A UserController) Login(w *api.JiaozifsResponse, r *http.Request) { } // Perform login - resp, err := login.Login(ctx, A.Auth) + resp, err := login.Login(ctx, A.Repo, A.Config) if err != nil { w.RespError(err) return @@ -45,13 +48,13 @@ func (A UserController) Register(w *api.JiaozifsResponse, r *http.Request) { w.RespError(err) } // Perform register - msg, err := register.Register(ctx, A.Auth) + err := register.Register(ctx, A.Repo) if err != nil { w.RespError(err) return } // resp - w.RespJSON(msg) + w.RespJSON("registration success") } func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { @@ -61,7 +64,7 @@ func (A UserController) GetUserInfo(w *api.JiaozifsResponse, r *http.Request) { userInfo := &auth.UserInfo{Token: tokenString} // Perform GetUserInfo - info, err := userInfo.UserProfile(ctx, A.Auth) + info, err := userInfo.UserProfile(ctx, A.Repo, A.Config) if err != nil { w.RespError(err) return From a79eab8d9d34a3d4899b7b3a15d8055cf0962846 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 20:57:59 +0800 Subject: [PATCH 16/20] chore: remove parameter references, remove resp msg, remove GetUserByNameEmail and add GetUserByEmail --- api/jiaozifs.gen.go | 110 +++++++++++++++++++------------------------- cmd/daemon.go | 12 ++--- models/user.go | 14 +++--- models/user_test.go | 9 ++-- 4 files changed, 63 insertions(+), 82 deletions(-) diff --git a/api/jiaozifs.gen.go b/api/jiaozifs.gen.go index cde0ac77..43ac5ca8 100644 --- a/api/jiaozifs.gen.go +++ b/api/jiaozifs.gen.go @@ -77,11 +77,6 @@ type ObjectStatsPathType string // ObjectUserMetadata defines model for ObjectUserMetadata. type ObjectUserMetadata map[string]string -// RegistrationMsg defines model for RegistrationMsg. -type RegistrationMsg struct { - Message string `json:"message"` -} - // UserInfo defines model for UserInfo. type UserInfo struct { CreatedAt *time.Time `json:"createdAt,omitempty"` @@ -910,7 +905,6 @@ func (r LoginResponse) StatusCode() int { type RegisterResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *RegistrationMsg } // Status returns HTTPResponse.Status @@ -1185,16 +1179,6 @@ func ParseRegisterResponse(rsp *http.Response) (*RegisterResponse, error) { HTTPResponse: rsp, } - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest RegistrationMsg - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - } - return response, nil } @@ -1446,7 +1430,7 @@ func (siw *ServerInterfaceWrapper) Register(w http.ResponseWriter, r *http.Reque func (siw *ServerInterfaceWrapper) GetUserInfo(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetUserInfo(&JiaozifsResponse{w}, r) @@ -1474,9 +1458,9 @@ func (siw *ServerInterfaceWrapper) DeleteObject(w http.ResponseWriter, r *http.R return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params DeleteObjectParams @@ -1522,9 +1506,9 @@ func (siw *ServerInterfaceWrapper) GetObject(w http.ResponseWriter, r *http.Requ return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params GetObjectParams @@ -1599,9 +1583,9 @@ func (siw *ServerInterfaceWrapper) HeadObject(w http.ResponseWriter, r *http.Req return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params HeadObjectParams @@ -1668,9 +1652,9 @@ func (siw *ServerInterfaceWrapper) UploadObject(w http.ResponseWriter, r *http.R return } - ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{"aaaa"}) + ctx = context.WithValue(ctx, Jwt_tokenScopes, []string{}) - ctx = context.WithValue(ctx, Basic_authScopes, []string{"aaaaa"}) + ctx = context.WithValue(ctx, Basic_authScopes, []string{}) // Parameter object where we will unmarshal all parameters from the context var params UploadObjectParams @@ -1891,44 +1875,44 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "2WK0+HcAAAD//87c0PRRIwAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/cmd/daemon.go b/cmd/daemon.go index bd32bd99..425a0e4c 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -3,8 +3,6 @@ package cmd import ( "context" - "github.com/jiaozifs/jiaozifs/auth" - "github.com/jiaozifs/jiaozifs/block/params" "github.com/jiaozifs/jiaozifs/block" @@ -49,17 +47,15 @@ var daemonCmd = &cobra.Command{ fx_opt.Override(new(*config.APIConfig), &cfg.API), fx_opt.Override(new(*config.DatabaseConfig), &cfg.Database), fx_opt.Override(new(params.AdapterConfig), &cfg.Blockstore), - //auth - fx_opt.Override(new(auth.Service), auth.NewAuthService), //blockstore fx_opt.Override(new(block.Adapter), factory.BuildBlockAdapter), //database fx_opt.Override(new(*bun.DB), models.SetupDatabase), fx_opt.Override(fx_opt.NextInvoke(), migrations.MigrateDatabase), - fx_opt.Override(new(*models.IUserRepo), models.NewUserRepo), - fx_opt.Override(new(*models.IRepositoryRepo), models.NewRepositoryRepo), - fx_opt.Override(new(*models.IRefRepo), models.NewRefRepo), - fx_opt.Override(new(*models.IObjectRepo), models.NewObjectRepo), + fx_opt.Override(new(models.IUserRepo), models.NewUserRepo), + fx_opt.Override(new(models.IRepositoryRepo), models.NewRepositoryRepo), + fx_opt.Override(new(models.IRefRepo), models.NewRefRepo), + fx_opt.Override(new(models.IObjectRepo), models.NewObjectRepo), //api fx_opt.Override(fx_opt.NextInvoke(), apiImpl.SetupAPI), diff --git a/models/user.go b/models/user.go index 87c2bd3e..1104a7df 100644 --- a/models/user.go +++ b/models/user.go @@ -29,7 +29,7 @@ type IUserRepo interface { GetEPByName(ctx context.Context, name string) (string, error) GetUserByName(ctx context.Context, name string) (*User, error) - CheckUserByNameEmail(ctx context.Context, name, email string) bool + GetUserByEmail(ctx context.Context, email string) (*User, error) } var _ IUserRepo = (*UserRepo)(nil) @@ -63,13 +63,13 @@ func (userRepo *UserRepo) GetEPByName(ctx context.Context, name string) (string, Scan(ctx, &ep) } -func (userRepo *UserRepo) CheckUserByNameEmail(ctx context.Context, name, email string) bool { - err := userRepo.DB.NewSelect().Model((*User)(nil)).Where("name = ? OR email = ?", name, email). - Limit(1).Scan(ctx) - return err == nil -} - func (userRepo *UserRepo) GetUserByName(ctx context.Context, name string) (*User, error) { user := &User{} return user, userRepo.DB.NewSelect().Model(user).Where("name = ?", name).Scan(ctx) } + +func (userRepo *UserRepo) GetUserByEmail(ctx context.Context, email string) (*User, error) { + user := &User{} + return user, userRepo.DB.NewSelect(). + Model(user).Where("email = ?", email).Scan(ctx) +} diff --git a/models/user_test.go b/models/user_test.go index 780ef9a6..d0ffc743 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -68,10 +68,11 @@ func TestNewUserRepo(t *testing.T) { require.NoError(t, err) require.True(t, cmp.Equal(userModel.EncryptedPassword, ep)) - b := repo.CheckUserByNameEmail(ctx, newUser.Name, newUser.Email) - require.True(t, !b) + userByEmail, err := repo.GetUserByEmail(ctx, newUser.Email) + require.NoError(t, err) + require.True(t, cmp.Equal(userModel.UpdatedAt, userByEmail.UpdatedAt, dbTimeCmpOpt)) - u, err := repo.GetUserByName(ctx, newUser.Name) + userByName, err := repo.GetUserByName(ctx, newUser.Name) require.NoError(t, err) - require.True(t, cmp.Equal(userModel.UpdatedAt, u.UpdatedAt, dbTimeCmpOpt)) + require.True(t, cmp.Equal(userModel.UpdatedAt, userByName.UpdatedAt, dbTimeCmpOpt)) } From 1deb9e8600bf996365e30d93a3b26a1ef8387535 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:00:09 +0800 Subject: [PATCH 17/20] fix: remove hash password --- auth/basic_auth.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 8fb8a641..ed07fb78 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -71,17 +71,11 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err return } - password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) - if err != nil { - log.Error(ErrComparePassword) - return - } - // insert db user := &models.User{ Name: r.Username, Email: r.Email, - EncryptedPassword: string(password), + EncryptedPassword: r.Password, CurrentSignInAt: time.Time{}, LastSignInAt: time.Time{}, CurrentSignInIP: "", From 69c43110c706dab152abf026712a41fecefa0873 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:01:45 +0800 Subject: [PATCH 18/20] Revert "fix: remove hash password" This reverts commit 1deb9e8600bf996365e30d93a3b26a1ef8387535. --- auth/basic_auth.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index ed07fb78..8fb8a641 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -71,11 +71,17 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err return } + password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) + if err != nil { + log.Error(ErrComparePassword) + return + } + // insert db user := &models.User{ Name: r.Username, Email: r.Email, - EncryptedPassword: r.Password, + EncryptedPassword: string(password), CurrentSignInAt: time.Time{}, LastSignInAt: time.Time{}, CurrentSignInIP: "", From 90de0593e21ed0944537efbea6eaab33ecb45598 Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:04:35 +0800 Subject: [PATCH 19/20] docs: add comments for bcrypt.GenerateFromPassword --- auth/basic_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 8fb8a641..98e6d160 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -70,7 +70,7 @@ func (r *Register) Register(ctx context.Context, repo models.IUserRepo) (err err log.Error(ErrInvalidNameEmail) return } - + // reserve temporarily password, err := bcrypt.GenerateFromPassword([]byte(r.Password), passwordCost) if err != nil { log.Error(ErrComparePassword) From d094a4502603d4b8f2779e654c53d1ba49fd913a Mon Sep 17 00:00:00 2001 From: zjy Date: Wed, 6 Dec 2023 21:05:24 +0800 Subject: [PATCH 20/20] style: goimports code --- auth/auth_test.go | 3 ++- auth/basic_auth.go | 3 ++- controller/user_ctl.go | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index 8ee1aad3..63b0d708 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -3,6 +3,8 @@ package auth import ( "context" "fmt" + "testing" + "github.com/brianvoe/gofakeit/v6" embeddedpostgres "github.com/fergusstrange/embedded-postgres" "github.com/jiaozifs/jiaozifs/config" @@ -12,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/uptrace/bun" "go.uber.org/fx/fxtest" - "testing" ) var testConnTmpl = "postgres://postgres:postgres@localhost:%d/jiaozifs?sslmode=disable" diff --git a/auth/basic_auth.go b/auth/basic_auth.go index 98e6d160..d69d3678 100644 --- a/auth/basic_auth.go +++ b/auth/basic_auth.go @@ -2,9 +2,10 @@ package auth import ( "context" - "github.com/jiaozifs/jiaozifs/config" "time" + "github.com/jiaozifs/jiaozifs/config" + "github.com/golang-jwt/jwt" openapi_types "github.com/oapi-codegen/runtime/types" diff --git a/controller/user_ctl.go b/controller/user_ctl.go index 1b159db8..221af5e6 100644 --- a/controller/user_ctl.go +++ b/controller/user_ctl.go @@ -2,9 +2,10 @@ package controller import ( "encoding/json" + "net/http" + "github.com/jiaozifs/jiaozifs/config" "github.com/jiaozifs/jiaozifs/models" - "net/http" "github.com/jiaozifs/jiaozifs/api" "github.com/jiaozifs/jiaozifs/auth"