diff --git a/server/backend/database/database.go b/server/backend/database/database.go index be0118e2f..d04a7ecc3 100644 --- a/server/backend/database/database.go +++ b/server/backend/database/database.go @@ -68,6 +68,12 @@ type Database interface { publicKey string, ) (*ProjectInfo, error) + // FindProjectInfoBySecretKey returns a project by secret key. + FindProjectInfoBySecretKey( + ctx context.Context, + secretKey string, + ) (*ProjectInfo, error) + // FindProjectInfoByName returns a project by the given name. FindProjectInfoByName( ctx context.Context, @@ -118,6 +124,9 @@ type Database interface { // FindUserInfo returns a user by the given username. FindUserInfo(ctx context.Context, username string) (*UserInfo, error) + // FindUserInfoByID finds a user by the given id. + FindUserInfoByID(ctx context.Context, id types.ID) (*UserInfo, error) + // ListUserInfos returns all users. ListUserInfos(ctx context.Context) ([]*UserInfo, error) diff --git a/server/backend/database/memory/database.go b/server/backend/database/memory/database.go index 7a45335cb..a8497268d 100644 --- a/server/backend/database/memory/database.go +++ b/server/backend/database/memory/database.go @@ -75,6 +75,25 @@ func (d *DB) FindProjectInfoByPublicKey( return raw.(*database.ProjectInfo).DeepCopy(), nil } +// FindProjectInfoBySecretKey returns a project by secret key. +func (d *DB) FindProjectInfoBySecretKey( + _ context.Context, + secretKey string, +) (*database.ProjectInfo, error) { + txn := d.db.Txn(false) + defer txn.Abort() + + raw, err := txn.First(tblProjects, "secret_key", secretKey) + if err != nil { + return nil, fmt.Errorf("find project by secret key: %w", err) + } + if raw == nil { + return nil, fmt.Errorf("%s: %w", secretKey, database.ErrProjectNotFound) + } + + return raw.(*database.ProjectInfo).DeepCopy(), nil +} + // FindProjectInfoByName returns a project by the given name. func (d *DB) FindProjectInfoByName( _ context.Context, @@ -379,6 +398,22 @@ func (d *DB) FindUserInfo(_ context.Context, username string) (*database.UserInf return raw.(*database.UserInfo).DeepCopy(), nil } +// FindUserInfoByID finds a user by the given ID. +func (d *DB) FindUserInfoByID(_ context.Context, clientID types.ID) (*database.UserInfo, error) { + txn := d.db.Txn(false) + defer txn.Abort() + + raw, err := txn.First(tblUsers, "id", clientID.String()) + if err != nil { + return nil, fmt.Errorf("find user by id: %w", err) + } + if raw == nil { + return nil, fmt.Errorf("%s: %w", clientID, database.ErrUserNotFound) + } + + return raw.(*database.UserInfo).DeepCopy(), nil +} + // ListUserInfos returns all users. func (d *DB) ListUserInfos(_ context.Context) ([]*database.UserInfo, error) { txn := d.db.Txn(false) diff --git a/server/backend/database/memory/database_test.go b/server/backend/database/memory/database_test.go index 2486f8bf1..2e247fcff 100644 --- a/server/backend/database/memory/database_test.go +++ b/server/backend/database/memory/database_test.go @@ -56,6 +56,14 @@ func TestDB(t *testing.T) { testcases.RunListUserInfosTest(t, db) }) + t.Run("FindUserInfoByID test", func(t *testing.T) { + testcases.RunFindUserInfoByIDTest(t, db) + }) + + t.Run("FindProjectInfoBySecretKey test", func(t *testing.T) { + testcases.RunFindProjectInfoBySecretKeyTest(t, db) + }) + t.Run("FindProjectInfoByName test", func(t *testing.T) { testcases.RunFindProjectInfoByNameTest(t, db) }) diff --git a/server/backend/database/mongo/client.go b/server/backend/database/mongo/client.go index 5429f58f7..602281419 100644 --- a/server/backend/database/mongo/client.go +++ b/server/backend/database/mongo/client.go @@ -313,6 +313,23 @@ func (c *Client) FindProjectInfoByPublicKey(ctx context.Context, publicKey strin return &projectInfo, nil } +// FindProjectInfoBySecretKey returns a project by secret key. +func (c *Client) FindProjectInfoBySecretKey(ctx context.Context, secretKey string) (*database.ProjectInfo, error) { + result := c.collection(colProjects).FindOne(ctx, bson.M{ + "secret_key": secretKey, + }) + + projectInfo := database.ProjectInfo{} + if err := result.Decode(&projectInfo); err != nil { + if err == mongo.ErrNoDocuments { + return nil, fmt.Errorf("%s: %w", secretKey, database.ErrProjectNotFound) + } + return nil, fmt.Errorf("decode project info: %w", err) + } + + return &projectInfo, nil +} + // FindProjectInfoByName returns a project by name. func (c *Client) FindProjectInfoByName( ctx context.Context, @@ -468,6 +485,28 @@ func (c *Client) ListUserInfos( return infos, nil } +// FindUserInfoByID returns a user by ID. +func (c *Client) FindUserInfoByID(ctx context.Context, clientID types.ID) (*database.UserInfo, error) { + encodedClientID, err := encodeID(clientID) + if err != nil { + return nil, err + } + + result := c.collection(colUsers).FindOne(ctx, bson.M{ + "_id": encodedClientID, + }) + + userInfo := database.UserInfo{} + if err := result.Decode(&userInfo); err != nil { + if err == mongo.ErrNoDocuments { + return nil, fmt.Errorf("%s: %w", clientID, database.ErrUserNotFound) + } + return nil, fmt.Errorf("decode user info: %w", err) + } + + return &userInfo, nil +} + // ActivateClient activates the client of the given key. func (c *Client) ActivateClient(ctx context.Context, projectID types.ID, key string) (*database.ClientInfo, error) { encodedProjectID, err := encodeID(projectID) diff --git a/server/backend/database/mongo/client_test.go b/server/backend/database/mongo/client_test.go index 9eaf04034..6b5ff9466 100644 --- a/server/backend/database/mongo/client_test.go +++ b/server/backend/database/mongo/client_test.go @@ -73,6 +73,14 @@ func TestClient(t *testing.T) { testcases.RunListUserInfosTest(t, cli) }) + t.Run("FindUserInfoByID test", func(t *testing.T) { + testcases.RunFindUserInfoByIDTest(t, cli) + }) + + t.Run("FindProjectInfoBySecretKey test", func(t *testing.T) { + testcases.RunFindProjectInfoBySecretKeyTest(t, cli) + }) + t.Run("FindProjectInfoByName test", func(t *testing.T) { testcases.RunFindProjectInfoByNameTest(t, cli) }) diff --git a/server/backend/database/testcases/testcases.go b/server/backend/database/testcases/testcases.go index 3cc19121c..ea7cb00e7 100644 --- a/server/backend/database/testcases/testcases.go +++ b/server/backend/database/testcases/testcases.go @@ -72,6 +72,27 @@ func RunFindDocInfoTest( }) } +// RunFindProjectInfoBySecretKeyTest runs the FindProjectInfoBySecretKey test for the given db. +func RunFindProjectInfoBySecretKeyTest( + t *testing.T, + db database.Database, +) { + t.Run("FindProjectInfoBySecretKey test", func(t *testing.T) { + ctx := context.Background() + + username := "admin@yorkie.dev" + password := "hashed-password" + + _, project, err := db.EnsureDefaultUserAndProject(ctx, username, password, clientDeactivateThreshold) + assert.NoError(t, err) + + info2, err := db.FindProjectInfoBySecretKey(ctx, project.SecretKey) + assert.NoError(t, err) + + assert.Equal(t, project.ID, info2.ID) + }) +} + // RunFindProjectInfoByNameTest runs the FindProjectInfoByName test for the given db. func RunFindProjectInfoByNameTest( t *testing.T, @@ -274,6 +295,24 @@ func RunListUserInfosTest(t *testing.T, db database.Database) { }) } +// RunFindUserInfoByIDTest runs the FindUserInfoByID test for the given db. +func RunFindUserInfoByIDTest(t *testing.T, db database.Database) { + t.Run("RunFindUserInfoByID test", func(t *testing.T) { + ctx := context.Background() + + username := "findUserInfoTestAccount" + password := "temporary-password" + + user, _, err := db.EnsureDefaultUserAndProject(ctx, username, password, clientDeactivateThreshold) + assert.NoError(t, err) + + info1, err := db.FindUserInfoByID(ctx, user.ID) + assert.NoError(t, err) + + assert.Equal(t, user.ID, info1.ID) + }) +} + // RunActivateClientDeactivateClientTest runs the ActivateClient and DeactivateClient tests for the given db. func RunActivateClientDeactivateClientTest(t *testing.T, db database.Database, projectID types.ID) { t.Run("activate and find client test", func(t *testing.T) { diff --git a/server/projects/projects.go b/server/projects/projects.go index ab9ee2370..956ced116 100644 --- a/server/projects/projects.go +++ b/server/projects/projects.go @@ -92,6 +92,7 @@ func UpdateProject( // GetProjectFromAPIKey returns a project from an API key. func GetProjectFromAPIKey(ctx context.Context, be *backend.Backend, apiKey string) (*types.Project, error) { + // TODO(hackerwins): Default project without API key should be allowed only in standalone mode. if apiKey == "" { info, err := be.DB.FindProjectInfoByID(ctx, database.DefaultProjectID) if err != nil { @@ -107,3 +108,13 @@ func GetProjectFromAPIKey(ctx context.Context, be *backend.Backend, apiKey strin return info.ToProject(), nil } + +// GetProjectFromSecretKey returns a project from a secret key. +func GetProjectFromSecretKey(ctx context.Context, be *backend.Backend, secretKey string) (*types.Project, error) { + info, err := be.DB.FindProjectInfoBySecretKey(ctx, secretKey) + if err != nil { + return nil, err + } + + return info.ToProject(), nil +} diff --git a/server/rpc/interceptors/admin_auth.go b/server/rpc/interceptors/admin_auth.go index 59f09602c..f46d6cf39 100644 --- a/server/rpc/interceptors/admin_auth.go +++ b/server/rpc/interceptors/admin_auth.go @@ -26,6 +26,7 @@ import ( "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/server/backend" + "github.com/yorkie-team/yorkie/server/projects" "github.com/yorkie-team/yorkie/server/rpc/auth" "github.com/yorkie-team/yorkie/server/rpc/connecthelper" "github.com/yorkie-team/yorkie/server/users" @@ -161,15 +162,23 @@ func (i *AdminAuthInterceptor) authenticate( return nil, connect.NewError(connect.CodeUnauthenticated, ErrUnauthenticated) } + // NOTE(raararaara): If the token is access token, return the user of the token. claims, err := i.tokenManager.Verify(authorization) - if err != nil { - return nil, connect.NewError(connect.CodeUnauthenticated, ErrUnauthenticated) + if err == nil { + user, err := users.GetUser(ctx, i.backend, claims.Username) + if err == nil { + return user, nil + } } - user, err := users.GetUser(ctx, i.backend, claims.Username) - if err != nil { - return nil, connect.NewError(connect.CodeUnauthenticated, ErrUnauthenticated) + // NOTE(raararaara): If the token is secret key, return the owner of the project. + project, err := projects.GetProjectFromSecretKey(ctx, i.backend, authorization) + if err == nil { + user, err := users.GetUserByID(ctx, i.backend, project.Owner) + if err == nil { + return user, nil + } } - return user, nil + return nil, connect.NewError(connect.CodeUnauthenticated, ErrUnauthenticated) } diff --git a/server/users/users.go b/server/users/users.go index e24d383f0..9f4019b43 100644 --- a/server/users/users.go +++ b/server/users/users.go @@ -81,3 +81,16 @@ func GetUser( return info.ToUser(), nil } + +// GetUserByID returns a user by ID. +func GetUserByID( + ctx context.Context, + be *backend.Backend, + id types.ID, +) (*types.User, error) { + info, err := be.DB.FindUserInfoByID(ctx, id) + if err != nil { + return nil, err + } + return info.ToUser(), nil +}