diff --git a/api/auth/auth.go b/api/auth/auth.go index e7ca7803..8abb5d3b 100644 --- a/api/auth/auth.go +++ b/api/auth/auth.go @@ -19,8 +19,6 @@ const ( // CookieExpDuration expires slightly earlier than the jwt expiration. Client would be logged out if the user // cookie expires, thus the client would always logout first before attempting to make a request with the expired jwt. - // Suppose we have a valid refresh token, we will refresh the token in cases: - // 1. The access token has already expired, we refresh the token so that the ongoing request can pass through. CookieExpDuration = AccessTokenDuration - 1*time.Minute // AccessTokenCookieName is the cookie name of access token. AccessTokenCookieName = "memos.access-token" diff --git a/api/v1/docs.go b/api/v1/docs.go index 2bd74b78..3828ece7 100644 --- a/api/v1/docs.go +++ b/api/v1/docs.go @@ -23,50 +23,6 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/api/v1/GetSystemStatus": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "system" - ], - "summary": "Get system GetSystemStatus", - "responses": { - "200": { - "description": "System GetSystemStatus", - "schema": { - "$ref": "#/definitions/v1.SystemStatus" - } - }, - "401": { - "description": "Missing user in session | Unauthorized" - }, - "500": { - "description": "Failed to find host user | Failed to find system setting list | Failed to unmarshal system setting customized profile value" - } - } - } - }, - "/api/v1/PingSystem": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "system" - ], - "summary": "Ping the system", - "responses": { - "200": { - "description": "System profile", - "schema": { - "$ref": "#/definitions/profile.Profile" - } - } - } - } - }, "/api/v1/auth/signin": { "post": { "consumes": [ @@ -1152,6 +1108,25 @@ const docTemplate = `{ } } }, + "/api/v1/ping": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "system" + ], + "summary": "Ping the system", + "responses": { + "200": { + "description": "If succeed to ping the system", + "schema": { + "type": "boolean" + } + } + } + } + }, "/api/v1/resource": { "get": { "security": [ @@ -1386,6 +1361,31 @@ const docTemplate = `{ } } }, + "/api/v1/status": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "system" + ], + "summary": "Get system GetSystemStatus", + "responses": { + "200": { + "description": "System GetSystemStatus", + "schema": { + "$ref": "#/definitions/v1.SystemStatus" + } + }, + "401": { + "description": "Missing user in session | Unauthorized" + }, + "500": { + "description": "Failed to find host user | Failed to find system setting list | Failed to unmarshal system setting customized profile value" + } + } + } + }, "/api/v1/storage": { "get": { "security": [ @@ -1555,36 +1555,6 @@ const docTemplate = `{ } } }, - "/api/v1/system/ExecVacuum": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "produces": [ - "application/json" - ], - "tags": [ - "system" - ], - "summary": "Vacuum the database", - "responses": { - "200": { - "description": "Database vacuumed", - "schema": { - "type": "boolean" - } - }, - "401": { - "description": "Missing user in session | Unauthorized" - }, - "500": { - "description": "Failed to find user | Failed to ExecVacuum database" - } - } - } - }, "/api/v1/system/setting": { "get": { "security": [ @@ -1666,6 +1636,36 @@ const docTemplate = `{ } } }, + "/api/v1/system/vacuum": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "system" + ], + "summary": "Vacuum the database", + "responses": { + "200": { + "description": "Database vacuumed", + "schema": { + "type": "boolean" + } + }, + "401": { + "description": "Missing user in session | Unauthorized" + }, + "500": { + "description": "Failed to find user | Failed to ExecVacuum database" + } + } + } + }, "/api/v1/tag": { "get": { "security": [ diff --git a/api/v1/jwt.go b/api/v1/jwt.go index af07f124..5192c6c3 100644 --- a/api/v1/jwt.go +++ b/api/v1/jwt.go @@ -37,7 +37,7 @@ func GenerateTokensAndSetCookies(c echo.Context, user *store.User, secret string return nil } -// RemoveTokensAndCookies removes the jwt token and refresh token from the cookies. +// RemoveTokensAndCookies removes the jwt token from the cookies. func RemoveTokensAndCookies(c echo.Context) { cookieExp := time.Now().Add(-1 * time.Hour) setTokenCookie(c, auth.AccessTokenCookieName, "", cookieExp) @@ -121,8 +121,6 @@ func audienceContains(audience jwt.ClaimStrings, token string) bool { } // JWTMiddleware validates the access token. -// If the access token is about to expire or has expired and the request has a valid refresh token, it -// will try to generate new access token and refresh token. func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) echo.HandlerFunc { return func(c echo.Context) error { ctx := c.Request().Context() @@ -172,7 +170,7 @@ func JWTMiddleware(server *APIV1Service, next echo.HandlerFunc, secret string) e return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName)) } - // We either have a valid access token or we will attempt to generate new access token and refresh token + // We either have a valid access token or we will attempt to generate new access token. userID, err := util.ConvertStringToInt32(claims.Subject) if err != nil { return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.") diff --git a/api/v1/swagger.yaml b/api/v1/swagger.yaml index 6933194a..f2b27e6d 100644 --- a/api/v1/swagger.yaml +++ b/api/v1/swagger.yaml @@ -748,35 +748,6 @@ info: title: memos API version: "1.0" paths: - /api/v1/GetSystemStatus: - get: - produces: - - application/json - responses: - "200": - description: System GetSystemStatus - schema: - $ref: '#/definitions/v1.SystemStatus' - "401": - description: Missing user in session | Unauthorized - "500": - description: Failed to find host user | Failed to find system setting list - | Failed to unmarshal system setting customized profile value - summary: Get system GetSystemStatus - tags: - - system - /api/v1/PingSystem: - get: - produces: - - application/json - responses: - "200": - description: System profile - schema: - $ref: '#/definitions/profile.Profile' - summary: Ping the system - tags: - - system /api/v1/auth/signin: post: consumes: @@ -1509,6 +1480,18 @@ paths: summary: Get memo stats by creator ID or username tags: - memo + /api/v1/ping: + get: + produces: + - application/json + responses: + "200": + description: If succeed to ping the system + schema: + type: boolean + summary: Ping the system + tags: + - system /api/v1/resource: get: parameters: @@ -1660,6 +1643,23 @@ paths: summary: Upload resource tags: - resource + /api/v1/status: + get: + produces: + - application/json + responses: + "200": + description: System GetSystemStatus + schema: + $ref: '#/definitions/v1.SystemStatus' + "401": + description: Missing user in session | Unauthorized + "500": + description: Failed to find host user | Failed to find system setting list + | Failed to unmarshal system setting customized profile value + summary: Get system GetSystemStatus + tags: + - system /api/v1/storage: get: produces: @@ -1769,24 +1769,6 @@ paths: summary: Update a storage tags: - storage - /api/v1/system/ExecVacuum: - post: - produces: - - application/json - responses: - "200": - description: Database vacuumed - schema: - type: boolean - "401": - description: Missing user in session | Unauthorized - "500": - description: Failed to find user | Failed to ExecVacuum database - security: - - ApiKeyAuth: [] - summary: Vacuum the database - tags: - - system /api/v1/system/setting: get: produces: @@ -1837,6 +1819,24 @@ paths: summary: Create system setting tags: - system-setting + /api/v1/system/vacuum: + post: + produces: + - application/json + responses: + "200": + description: Database vacuumed + schema: + type: boolean + "401": + description: Missing user in session | Unauthorized + "500": + description: Failed to find user | Failed to ExecVacuum database + security: + - ApiKeyAuth: [] + summary: Vacuum the database + tags: + - system /api/v1/tag: get: produces: diff --git a/api/v1/system.go b/api/v1/system.go index 70a67f53..72e649f9 100644 --- a/api/v1/system.go +++ b/api/v1/system.go @@ -53,10 +53,10 @@ func (s *APIV1Service) registerSystemRoutes(g *echo.Group) { // @Summary Ping the system // @Tags system // @Produce json -// @Success 200 {object} profile.Profile "System profile" -// @Router /api/v1/PingSystem [GET] -func (s *APIV1Service) PingSystem(c echo.Context) error { - return c.JSON(http.StatusOK, s.Profile) +// @Success 200 {boolean} true "If succeed to ping the system" +// @Router /api/v1/ping [GET] +func (*APIV1Service) PingSystem(c echo.Context) error { + return c.JSON(http.StatusOK, true) } // GetSystemStatus godoc @@ -67,7 +67,7 @@ func (s *APIV1Service) PingSystem(c echo.Context) error { // @Success 200 {object} SystemStatus "System GetSystemStatus" // @Failure 401 {object} nil "Missing user in session | Unauthorized" // @Failure 500 {object} nil "Failed to find host user | Failed to find system setting list | Failed to unmarshal system setting customized profile value" -// @Router /api/v1/GetSystemStatus [GET] +// @Router /api/v1/status [GET] func (s *APIV1Service) GetSystemStatus(c echo.Context) error { ctx := c.Request().Context() @@ -165,7 +165,7 @@ func (s *APIV1Service) GetSystemStatus(c echo.Context) error { // @Failure 401 {object} nil "Missing user in session | Unauthorized" // @Failure 500 {object} nil "Failed to find user | Failed to ExecVacuum database" // @Security ApiKeyAuth -// @Router /api/v1/system/ExecVacuum [POST] +// @Router /api/v1/system/vacuum [POST] func (s *APIV1Service) ExecVacuum(c echo.Context) error { ctx := c.Request().Context() userID, ok := c.Get(auth.UserIDContextKey).(int32) diff --git a/plugin/idp/oauth2/oauth2_test.go b/plugin/idp/oauth2/oauth2_test.go index e14e6168..035333d2 100644 --- a/plugin/idp/oauth2/oauth2_test.go +++ b/plugin/idp/oauth2/oauth2_test.go @@ -90,11 +90,10 @@ func newMockServer(t *testing.T, code, accessToken string, userinfo []byte) *htt w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(map[string]any{ - "access_token": accessToken, - "token_type": "Bearer", - "refresh_token": "test-refresh-token", - "expires_in": 3600, - "id_token": rawIDToken, + "access_token": accessToken, + "token_type": "Bearer", + "expires_in": 3600, + "id_token": rawIDToken, }) require.NoError(t, err) }) diff --git a/test/server/auth_test.go b/test/server/auth_test.go index 79576896..98dba514 100644 --- a/test/server/auth_test.go +++ b/test/server/auth_test.go @@ -21,12 +21,24 @@ func TestAuthServer(t *testing.T) { Username: "testuser", Password: "testpassword", } - user, err := s.postAuthSignup(signup) + user, err := s.postAuthSignUp(signup) require.NoError(t, err) require.Equal(t, signup.Username, user.Username) + + signin := &apiv1.SignIn{ + Username: "testuser", + Password: "testpassword", + } + user, err = s.postAuthSignIn(signin) + require.NoError(t, err) + require.Equal(t, signup.Username, user.Username) + err = s.postSignOut() + require.NoError(t, err) + _, err = s.getCurrentUser() + require.Error(t, err) } -func (s *TestingServer) postAuthSignup(signup *apiv1.SignUp) (*apiv1.User, error) { +func (s *TestingServer) postAuthSignUp(signup *apiv1.SignUp) (*apiv1.User, error) { rawData, err := json.Marshal(&signup) if err != nil { return nil, errors.Wrap(err, "failed to marshal signup") @@ -49,3 +61,35 @@ func (s *TestingServer) postAuthSignup(signup *apiv1.SignUp) (*apiv1.User, error } return user, nil } + +func (s *TestingServer) postAuthSignIn(signip *apiv1.SignIn) (*apiv1.User, error) { + rawData, err := json.Marshal(&signip) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal signin") + } + reader := bytes.NewReader(rawData) + body, err := s.post("/api/v1/auth/signin", reader, nil) + if err != nil { + return nil, errors.Wrap(err, "fail to post request") + } + + buf := &bytes.Buffer{} + _, err = buf.ReadFrom(body) + if err != nil { + return nil, errors.Wrap(err, "fail to read response body") + } + + user := &apiv1.User{} + if err = json.Unmarshal(buf.Bytes(), user); err != nil { + return nil, errors.Wrap(err, "fail to unmarshal post signin response") + } + return user, nil +} + +func (s *TestingServer) postSignOut() error { + _, err := s.post("/api/v1/auth/signout", nil, nil) + if err != nil { + return errors.Wrap(err, "fail to post request") + } + return nil +} diff --git a/test/server/memo_relation_test.go b/test/server/memo_relation_test.go index ca4deade..66103541 100644 --- a/test/server/memo_relation_test.go +++ b/test/server/memo_relation_test.go @@ -22,7 +22,7 @@ func TestMemoRelationServer(t *testing.T) { Username: "testuser", Password: "testpassword", } - user, err := s.postAuthSignup(signup) + user, err := s.postAuthSignUp(signup) require.NoError(t, err) require.Equal(t, signup.Username, user.Username) memo, err := s.postMemoCreate(&apiv1.CreateMemoRequest{ diff --git a/test/server/memo_test.go b/test/server/memo_test.go index 5a24435b..fee153b9 100644 --- a/test/server/memo_test.go +++ b/test/server/memo_test.go @@ -22,7 +22,7 @@ func TestMemoServer(t *testing.T) { Username: "testuser", Password: "testpassword", } - user, err := s.postAuthSignup(signup) + user, err := s.postAuthSignUp(signup) require.NoError(t, err) require.Equal(t, signup.Username, user.Username) memo, err := s.postMemoCreate(&apiv1.CreateMemoRequest{ diff --git a/test/server/server.go b/test/server/server.go index 79694e34..483138a9 100644 --- a/test/server/server.go +++ b/test/server/server.go @@ -142,7 +142,7 @@ func (s *TestingServer) request(method, uri string, body io.Reader, params, head return nil, errors.Errorf("unable to find access token in the login response headers") } s.cookie = cookie - } else if strings.Contains(uri, "/api/v1/auth/logout") { + } else if strings.Contains(uri, "/api/v1/auth/signout") { s.cookie = "" } } diff --git a/test/server/system_test.go b/test/server/system_test.go index d743590a..7e0a8ce4 100644 --- a/test/server/system_test.go +++ b/test/server/system_test.go @@ -25,15 +25,24 @@ func TestSystemServer(t *testing.T) { Username: "testuser", Password: "testpassword", } - user, err := s.postAuthSignup(signup) + user, err := s.postAuthSignUp(signup) require.NoError(t, err) require.Equal(t, signup.Username, user.Username) - + err = s.pingSystem() + require.NoError(t, err) status, err = s.getSystemStatus() require.NoError(t, err) require.Equal(t, user.ID, status.Host.ID) } +func (s *TestingServer) pingSystem() error { + _, err := s.get("/api/v1/ping", nil) + if err != nil { + return err + } + return nil +} + func (s *TestingServer) getSystemStatus() (*apiv1.SystemStatus, error) { body, err := s.get("/api/v1/status", nil) if err != nil { diff --git a/test/server/user_test.go b/test/server/user_test.go new file mode 100644 index 00000000..425364d3 --- /dev/null +++ b/test/server/user_test.go @@ -0,0 +1,103 @@ +package testserver + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + apiv1 "github.com/usememos/memos/api/v1" +) + +func TestUserServer(t *testing.T) { + ctx := context.Background() + s, err := NewTestingServer(ctx, t) + require.NoError(t, err) + defer s.Shutdown(ctx) + + signup := &apiv1.SignUp{ + Username: "testuser", + Password: "testpassword", + } + user, err := s.postAuthSignUp(signup) + require.NoError(t, err) + require.Equal(t, signup.Username, user.Username) + user, err = s.getCurrentUser() + require.NoError(t, err) + require.Equal(t, signup.Username, user.Username) + user, err = s.getUserByID(user.ID) + require.NoError(t, err) + require.Equal(t, signup.Username, user.Username) + newEmail := "test@usermemos.com" + userPatch := &apiv1.UpdateUserRequest{ + Email: &newEmail, + } + user, err = s.patchUser(user.ID, userPatch) + require.NoError(t, err) + require.Equal(t, newEmail, user.Email) +} + +func (s *TestingServer) getCurrentUser() (*apiv1.User, error) { + body, err := s.get("/api/v1/user/me", nil) + if err != nil { + return nil, err + } + + buf := &bytes.Buffer{} + _, err = buf.ReadFrom(body) + if err != nil { + return nil, errors.Wrap(err, "fail to read response body") + } + + user := &apiv1.User{} + if err = json.Unmarshal(buf.Bytes(), &user); err != nil { + return nil, errors.Wrap(err, "fail to unmarshal get user response") + } + return user, nil +} + +func (s *TestingServer) getUserByID(userID int32) (*apiv1.User, error) { + body, err := s.get(fmt.Sprintf("/api/v1/user/%d", userID), nil) + if err != nil { + return nil, err + } + + buf := &bytes.Buffer{} + _, err = buf.ReadFrom(body) + if err != nil { + return nil, errors.Wrap(err, "fail to read response body") + } + + user := &apiv1.User{} + if err = json.Unmarshal(buf.Bytes(), &user); err != nil { + return nil, errors.Wrap(err, "fail to unmarshal get user response") + } + return user, nil +} + +func (s *TestingServer) patchUser(userID int32, request *apiv1.UpdateUserRequest) (*apiv1.User, error) { + rawData, err := json.Marshal(&request) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal request") + } + reader := bytes.NewReader(rawData) + body, err := s.patch(fmt.Sprintf("/api/v1/user/%d", userID), reader, nil) + if err != nil { + return nil, err + } + + buf := &bytes.Buffer{} + _, err = buf.ReadFrom(body) + if err != nil { + return nil, errors.Wrap(err, "fail to read response body") + } + + user := &apiv1.User{} + if err = json.Unmarshal(buf.Bytes(), user); err != nil { + return nil, errors.Wrap(err, "fail to unmarshal patch user response") + } + return user, nil +}