Repository: yorkie-team/yorkie
Pull request #384 (2022-08-06 11:21:25)

Extract admin settings with flags and configurations
<!--  Thanks for sending a pull request! -->

**What this PR does / why we need it**:

Extract admin settings with flags and configurations.

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Address #377

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note

```

**Additional documentation**:

<!--
This section can be blank if this pull request does not require a release note.

Please use the following format for linking documentation:
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

**Checklist**:
- [x] Added relevant tests or not required
- [x] Didn't break anything


codecov[bot]: # [Codecov](https://codecov.io/gh/yorkie-team/yorkie/pull/384?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
> Merging [#384](https://codecov.io/gh/yorkie-team/yorkie/pull/384?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (7c7ee9f) into [main](https://codecov.io/gh/yorkie-team/yorkie/commit/7e333b82b657d2b6ad9d07d36c1b0a2e7da55f35?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (7e333b8) will **increase** coverage by `0.00%`.
> The diff coverage is `34.78%`.

```diff
@@           Coverage Diff           @@
##             main     #384   +/-   ##
=======================================
  Coverage   47.22%   47.22%           
=======================================
  Files          68       68           
  Lines        5741     5757   +16     
=======================================
+ Hits         2711     2719    +8     
- Misses       2734     2742    +8     
  Partials      296      296           
```


| [Impacted Files](https://codecov.io/gh/yorkie-team/yorkie/pull/384?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) | Coverage Δ | |
|---|---|---|
| [server/backend/backend.go](https://codecov.io/gh/yorkie-team/yorkie/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvYmFja2VuZC5nbw==) | `0.00% <0.00%> (ø)` | |
| [server/backend/config.go](https://codecov.io/gh/yorkie-team/yorkie/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvY29uZmlnLmdv) | `54.28% <0.00%> (-7.01%)` | :arrow_down: |
| [server/backend/database/memory/database.go](https://codecov.io/gh/yorkie-team/yorkie/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbWVtb3J5L2RhdGFiYXNlLmdv) | `46.70% <0.00%> (-0.18%)` | :arrow_down: |
| [server/backend/database/mongo/client.go](https://codecov.io/gh/yorkie-team/yorkie/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvbW9uZ28vY2xpZW50Lmdv) | `7.76% <0.00%> (ø)` | |
| [server/backend/database/user\_info.go](https://codecov.io/gh/yorkie-team/yorkie/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2JhY2tlbmQvZGF0YWJhc2UvdXNlcl9pbmZvLmdv) | `0.00% <ø> (ø)` | |
| [server/config.go](https://codecov.io/gh/yorkie-team/yorkie/pull/384/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-c2VydmVyL2NvbmZpZy5nbw==) | `43.47% <100.00%> (+5.38%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team)


Patch:
diff --git a/cmd/yorkie/server.go b/cmd/yorkie/server.go
index 03a43a59..7906f0dd 100644
--- a/cmd/yorkie/server.go
+++ b/cmd/yorkie/server.go
@@ -39,6 +39,7 @@ var (
 	flagConfPath string
 	flagLogLevel string
 
+	adminTokenDuration              time.Duration
 	housekeepingInterval            time.Duration
 	housekeepingDeactivateThreshold time.Duration
 
@@ -65,6 +66,7 @@ func newServerCmd() *cobra.Command {
 		Use:   "server [options]",
 		Short: "Start Yorkie server",
 		RunE: func(cmd *cobra.Command, args []string) error {
+			conf.Backend.AdminTokenDuration = adminTokenDuration.String()
 			conf.Backend.AuthWebhookMaxWaitInterval = authWebhookMaxWaitInterval.String()
 			conf.Backend.AuthWebhookCacheAuthTTL = authWebhookCacheAuthTTL.String()
 			conf.Backend.AuthWebhookCacheUnauthTTL = authWebhookCacheUnauthTTL.String()
@@ -288,6 +290,30 @@ func init() {
 		etcd.DefaultLockLeaseTime,
 		"ETCD's lease time for lock",
 	)
+	cmd.Flags().StringVar(
+		&conf.Backend.AdminUser,
+		"backend-admin-user",
+		server.DefaultAdminUser,
+		"The name of the default admin user, who has full permissions.",
+	)
+	cmd.Flags().StringVar(
+		&conf.Backend.AdminPassword,
+		"backend-admin-password",
+		server.DefaultAdminPassword,
+		"The password of the default admin.",
+	)
+	cmd.Flags().StringVar(
+		&conf.Backend.SecretKey,
+		"backend-secret-key",
+		server.DefaultSecretKey,
+		"The secret key for signing authentication tokens for admin users.",
+	)
+	cmd.Flags().DurationVar(
+		&adminTokenDuration,
+		"backend-admin-token-duration",
+		server.DefaultAdminTokenDuration,
+		"The duration of the admin authentication token.",
+	)
 	cmd.Flags().BoolVar(
 		&conf.Backend.UseDefaultProject,
 		"backend-use-default-project",
diff --git a/server/admin/server.go b/server/admin/server.go
index b1942d69..fc2fa63f 100644
--- a/server/admin/server.go
+++ b/server/admin/server.go
@@ -21,7 +21,6 @@ import (
 	"errors"
 	"fmt"
 	"net"
-	gotime "time"
 
 	grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	"google.golang.org/grpc"
@@ -68,8 +67,10 @@ type Server struct {
 
 // NewServer creates a new Server.
 func NewServer(conf *Config, be *backend.Backend) *Server {
-	// TODO(hackerwins): extract secretKey and tokenDuration to config.
-	tokenManager := auth.NewTokenManager("yorkie", 7*24*gotime.Hour)
+	tokenManager := auth.NewTokenManager(
+		be.Config.SecretKey,
+		be.Config.ParseAdminTokenDuration(),
+	)
 
 	loggingInterceptor := grpchelper.NewLoggingInterceptor()
 	authInterceptor := interceptors.NewAuthInterceptor(tokenManager)
diff --git a/server/backend/backend.go b/server/backend/backend.go
index ab2c021a..7b15651a 100644
--- a/server/backend/backend.go
+++ b/server/backend/backend.go
@@ -131,6 +131,8 @@ func New(
 
 	_, _, err = db.EnsureDefaultUserAndProject(
 		context.Background(),
+		conf.AdminUser,
+		conf.AdminPassword,
 	)
 	if err != nil {
 		return nil, err
diff --git a/server/backend/config.go b/server/backend/config.go
index 5bf37b6a..ac8aa028 100644
--- a/server/backend/config.go
+++ b/server/backend/config.go
@@ -23,6 +23,19 @@ import (
 
 // Config is the configuration for creating a Backend instance.
 type Config struct {
+	// AdminUser is the name of the default admin user who has full permissions.
+	// Set once on first-run. Default is "admin".
+	AdminUser string `yaml:"AdminUser"`
+
+	// AdminPassword is the password of the default admin. Default is "admin".
+	AdminPassword string `yaml:"AdminPassword"`
+
+	// SecretKey is the secret key for signing authentication tokens.
+	SecretKey string `yaml:"SecretKey"`
+
+	// AdminTokenDuration is the duration of the admin token. Default is "7d".
+	AdminTokenDuration string `yaml:"AdminTokenDuration"`
+
 	// UseDefaultProject is whether to use the default project. Even if public
 	// key is not provided from the client, the default project will be used. If
 	// we are using server as single-tenant mode, this should be set to true.
@@ -80,6 +93,16 @@ func (c *Config) Validate() error {
 	return nil
 }
 
+// ParseAdminTokenDuration returns admin token duration.
+func (c *Config) ParseAdminTokenDuration() time.Duration {
+	result, err := time.ParseDuration(c.AdminTokenDuration)
+	if err != nil {
+		panic(err)
+	}
+
+	return result
+}
+
 // ParseAuthWebhookMaxWaitInterval returns max wait interval.
 func (c *Config) ParseAuthWebhookMaxWaitInterval() time.Duration {
 	result, err := time.ParseDuration(c.AuthWebhookMaxWaitInterval)
diff --git a/server/backend/database/database.go b/server/backend/database/database.go
index 3e80b076..b25c99e9 100644
--- a/server/backend/database/database.go
+++ b/server/backend/database/database.go
@@ -71,6 +71,8 @@ type Database interface {
 	// EnsureDefaultUserAndProject ensures that the default user and project exists.
 	EnsureDefaultUserAndProject(
 		ctx context.Context,
+		username,
+		password string,
 	) (*UserInfo, *ProjectInfo, error)
 
 	// CreateProjectInfo creates a new project.
diff --git a/server/backend/database/memory/database.go b/server/backend/database/memory/database.go
index a89dd1e2..b66e8dd8 100644
--- a/server/backend/database/memory/database.go
+++ b/server/backend/database/memory/database.go
@@ -103,8 +103,12 @@ func (d *DB) FindProjectInfoByID(ctx context.Context, id types.ID) (*database.Pr
 }
 
 // EnsureDefaultUserAndProject creates the default user and project if they do not exist.
-func (d *DB) EnsureDefaultUserAndProject(ctx context.Context) (*database.UserInfo, *database.ProjectInfo, error) {
-	user, err := d.ensureDefaultUserInfo(ctx)
+func (d *DB) EnsureDefaultUserAndProject(
+	ctx context.Context,
+	username,
+	password string,
+) (*database.UserInfo, *database.ProjectInfo, error) {
+	user, err := d.ensureDefaultUserInfo(ctx, username, password)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -118,22 +122,26 @@ func (d *DB) EnsureDefaultUserAndProject(ctx context.Context) (*database.UserInf
 }
 
 // ensureDefaultUserInfo creates the default user if it does not exist.
-func (d *DB) ensureDefaultUserInfo(ctx context.Context) (*database.UserInfo, error) {
+func (d *DB) ensureDefaultUserInfo(
+	ctx context.Context,
+	username,
+	password string,
+) (*database.UserInfo, error) {
 	txn := d.db.Txn(true)
 	defer txn.Abort()
 
-	raw, err := txn.First(tblUsers, "username", database.DefaultUsername)
+	raw, err := txn.First(tblUsers, "username", username)
 	if err != nil {
 		return nil, err
 	}
 
 	var info *database.UserInfo
 	if raw == nil {
-		hashedPassword, err := database.HashedPassword(database.DefaultPassword)
+		hashedPassword, err := database.HashedPassword(password)
 		if err != nil {
 			return nil, err
 		}
-		info = database.NewUserInfo(database.DefaultUsername, hashedPassword)
+		info = database.NewUserInfo(username, hashedPassword)
 		info.ID = newID()
 		if err := txn.Insert(tblUsers, info); err != nil {
 			return nil, err
diff --git a/server/backend/database/mongo/client.go b/server/backend/database/mongo/client.go
index 6d28464a..391ddc7e 100644
--- a/server/backend/database/mongo/client.go
+++ b/server/backend/database/mongo/client.go
@@ -95,8 +95,10 @@ func (c *Client) Close() error {
 // EnsureDefaultUserAndProject creates the default user and project if they do not exist.
 func (c *Client) EnsureDefaultUserAndProject(
 	ctx context.Context,
+	username,
+	password string,
 ) (*database.UserInfo, *database.ProjectInfo, error) {
-	userInfo, err := c.ensureDefaultUserInfo(ctx)
+	userInfo, err := c.ensureDefaultUserInfo(ctx, username, password)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -112,14 +114,16 @@ func (c *Client) EnsureDefaultUserAndProject(
 // ensureDefaultUserInfo creates the default user info if it does not exist.
 func (c *Client) ensureDefaultUserInfo(
 	ctx context.Context,
+	username,
+	password string,
 ) (*database.UserInfo, error) {
-	hashedPassword, err := database.HashedPassword(database.DefaultPassword)
+	hashedPassword, err := database.HashedPassword(password)
 	if err != nil {
 		return nil, err
 	}
 
 	candidate := database.NewUserInfo(
-		database.DefaultUsername,
+		username,
 		hashedPassword,
 	)
 
diff --git a/server/backend/database/user_info.go b/server/backend/database/user_info.go
index b19b9a32..99cfd517 100644
--- a/server/backend/database/user_info.go
+++ b/server/backend/database/user_info.go
@@ -30,15 +30,6 @@ var (
 	ErrMismatchedPassword = fmt.Errorf("mismatched password")
 )
 
-// TODO(hackerwins): Extract this to a config file.
-var (
-	// DefaultUsername is the default username of the user who has full permissions.
-	DefaultUsername = "admin"
-
-	// DefaultPassword is the default password of the default user.
-	DefaultPassword = "admin"
-)
-
 // UserInfo is a structure representing information of a user.
 type UserInfo struct {
 	ID             types.ID  `bson:"_id"`
diff --git a/server/config.go b/server/config.go
index 3b76cf16..d6af70a0 100644
--- a/server/config.go
+++ b/server/config.go
@@ -52,9 +52,13 @@ const (
 	DefaultMongoPingTimeout       = 5 * time.Second
 	DefaultMongoYorkieDatabase    = "yorkie-meta"
 
-	DefaultUseDefaultProject = true
-	DefaultSnapshotThreshold = 500
-	DefaultSnapshotInterval  = 1000
+	DefaultAdminUser          = "admin"
+	DefaultAdminPassword      = "admin"
+	DefaultSecretKey          = "yorkie-secret"
+	DefaultAdminTokenDuration = 7 * 24 * time.Hour
+	DefaultUseDefaultProject  = true
+	DefaultSnapshotThreshold  = 500
+	DefaultSnapshotInterval   = 1000
 
 	DefaultAuthWebhookMaxRetries      = 10
 	DefaultAuthWebhookMaxWaitInterval = 3000 * time.Millisecond
@@ -161,6 +165,22 @@ func (c *Config) ensureDefaultValue() {
 		c.Admin.Port = DefaultAdminPort
 	}
 
+	if c.Backend.AdminUser == "" {
+		c.Backend.AdminUser = DefaultAdminUser
+	}
+
+	if c.Backend.AdminPassword == "" {
+		c.Backend.AdminPassword = DefaultAdminPassword
+	}
+
+	if c.Backend.SecretKey == "" {
+		c.Backend.SecretKey = DefaultSecretKey
+	}
+
+	if c.Backend.AdminTokenDuration == "" {
+		c.Backend.AdminTokenDuration = DefaultAdminTokenDuration.String()
+	}
+
 	if c.Backend.SnapshotThreshold == 0 {
 		c.Backend.SnapshotThreshold = DefaultSnapshotThreshold
 	}


Test patch:
diff --git a/test/helper/helper.go b/test/helper/helper.go
index 296d35ca..49b5749d 100644
--- a/test/helper/helper.go
+++ b/test/helper/helper.go
@@ -31,7 +31,6 @@ import (
 	"github.com/yorkie-team/yorkie/server"
 	"github.com/yorkie-team/yorkie/server/admin"
 	"github.com/yorkie-team/yorkie/server/backend"
-	"github.com/yorkie-team/yorkie/server/backend/database"
 	"github.com/yorkie-team/yorkie/server/backend/database/mongo"
 	"github.com/yorkie-team/yorkie/server/backend/housekeeping"
 	"github.com/yorkie-team/yorkie/server/backend/sync/etcd"
@@ -85,7 +84,7 @@ func CreateAdminCli(t assert.TestingT, adminAddr string) *adminClient.Client {
 	adminCli, err := adminClient.Dial(adminAddr)
 	assert.NoError(t, err)
 
-	_, err = adminCli.LogIn(context.Background(), database.DefaultUsername, database.DefaultPassword)
+	_, err = adminCli.LogIn(context.Background(), server.DefaultAdminUser, server.DefaultAdminPassword)
 	assert.NoError(t, err)
 
 	return adminCli
@@ -127,6 +126,10 @@ func TestConfig() *server.Config {
 			CandidatesLimit:     HousekeepingCandidatesLimit,
 		},
 		Backend: &backend.Config{
+			AdminUser:                  server.DefaultAdminUser,
+			AdminPassword:              server.DefaultAdminPassword,
+			SecretKey:                  server.DefaultSecretKey,
+			AdminTokenDuration:         server.DefaultAdminTokenDuration.String(),
 			UseDefaultProject:          true,
 			SnapshotThreshold:          SnapshotThreshold,
 			AuthWebhookMaxWaitInterval: AuthWebhookMaxWaitInterval.String(),