Repository: dragonflyoss/Dragonfly2
Pull request #529 (2021-08-11 06:36:09)

Support oauth
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate):

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] Code compiles correctly.
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [x] All new and existing tests passed.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/529?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#529](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/529?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (d4042be) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/9a0d057bcb13122d5385f72faeb1ab2efc07125e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (9a0d057) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/529/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/529?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@           Coverage Diff           @@
##             main     #529   +/-   ##
=======================================
  Coverage   39.48%   39.48%           
=======================================
  Files          86       86           
  Lines        6536     6536           
=======================================
  Hits         2581     2581           
  Misses       3584     3584           
  Partials      371      371           
```

| Flag | Coverage Δ | |
|---|---|---|
| e2etests | `?` | |
| unittests | `39.48% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/529?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [client/daemon/peer/peertask\_base.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/529/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2Jhc2UuZ28=) | `34.97% <0.00%> (ø)` | |


Patch:
diff --git a/client/daemon/peer/peertask_base.go b/client/daemon/peer/peertask_base.go
index f668db86..f4094d12 100644
--- a/client/daemon/peer/peertask_base.go
+++ b/client/daemon/peer/peertask_base.go
@@ -47,6 +47,7 @@ const (
 	reasonReScheduleTimeout     = "wait more available peers from scheduler timeout"
 	reasonContextCanceled       = "context canceled"
 	reasonPeerGoneFromScheduler = "scheduler says client should disconnect"
+	reasonBackSourceDisabled    = "download from source disabled"
 
 	failedReasonNotSet = "unknown"
 	failedCodeNotSet   = 0
@@ -103,8 +104,8 @@ type peerTask struct {
 
 	// done channel will be close when peer task is finished
 	done chan struct{}
-	// peerTaskDone will be true after peer task done
-	peerTaskDone bool
+	// success will be true after peer task done
+	success bool
 	// span stands open telemetry trace span
 	span trace.Span
 
@@ -112,7 +113,7 @@ type peerTask struct {
 	once sync.Once
 
 	// failedPieceCh will hold all pieces which download failed,
-	// those pieces will be retry later
+	// those pieces will be retried later
 	failedPieceCh chan int32
 	// failedReason will be set when peer task failed
 	failedReason string
@@ -225,6 +226,10 @@ loop:
 			break loop
 		}
 		if err != nil {
+			// when success, context will be cancelled, check if pt.success is true
+			if pt.success {
+				return
+			}
 			pt.failedCode = dfcodes.UnknownError
 			if de, ok := err.(*dferrors.DfError); ok {
 				if de.Code == dfcodes.SchedNeedBackSource {
@@ -387,7 +392,7 @@ loop:
 			break loop
 		case <-pt.ctx.Done():
 			pt.Debugf("context done due to %s", pt.ctx.Err())
-			if !pt.peerTaskDone {
+			if !pt.success {
 				if pt.failedCode == failedCodeNotSet {
 					pt.failedReason = reasonContextCanceled
 					pt.failedCode = dfcodes.ClientContextCanceled
@@ -487,20 +492,38 @@ func (pt *peerTask) waitFirstPeerPacket() bool {
 			pt.failedReason = err.Error()
 		}
 		pt.span.AddEvent(fmt.Sprintf("pulling pieces end due to %s", err))
-	case <-pt.peerPacketReady:
-		// preparePieceTasksByPeer func already send piece result with error
-		pt.Infof("new peer client ready, scheduler time cost: %dus, main peer: %s",
-			time.Now().Sub(pt.callback.GetStartTime()).Microseconds(), pt.peerPacket.Load().(*scheduler.PeerPacket).MainPeer)
-		return true
+	case _, ok := <-pt.peerPacketReady:
+		if ok {
+			// preparePieceTasksByPeer func already send piece result with error
+			pt.Infof("new peer client ready, scheduler time cost: %dus, main peer: %s",
+				time.Now().Sub(pt.callback.GetStartTime()).Microseconds(), pt.peerPacket.Load().(*scheduler.PeerPacket).MainPeer)
+			return true
+		}
+		// when schedule timeout, receivePeerPacket will close pt.peerPacketReady
+		if pt.schedulerOption.DisableAutoBackSource {
+			pt.failedReason = reasonBackSourceDisabled
+			err := fmt.Errorf("%s, auto back source disabled", pt.failedReason)
+			pt.span.RecordError(err)
+			pt.Errorf(err.Error())
+		} else {
+			pt.Warnf("start download from source due to dfcodes.SchedNeedBackSource")
+			pt.span.AddEvent("back source due to scheduler says need back source")
+			pt.needBackSource = true
+			pt.backSource()
+		}
 	case <-time.After(pt.schedulerOption.ScheduleTimeout.Duration):
 		if pt.schedulerOption.DisableAutoBackSource {
 			pt.failedReason = reasonScheduleTimeout
 			pt.failedCode = dfcodes.ClientScheduleTimeout
-			logger.Errorf("%s, auto back source disabled", pt.failedReason)
+			err := fmt.Errorf("%s, auto back source disabled", pt.failedReason)
+			pt.span.RecordError(err)
+			pt.Errorf(err.Error())
+		} else {
+			pt.Warnf("start download from source due to %s", reasonScheduleTimeout)
+			pt.span.AddEvent("back source due to schedule timeout")
+			pt.needBackSource = true
+			pt.backSource()
 		}
-		pt.Errorf("start download from source due to %s", reasonScheduleTimeout)
-		pt.needBackSource = true
-		pt.backSource()
 	}
 	return false
 }
@@ -510,10 +533,10 @@ func (pt *peerTask) waitAvailablePeerPacket() (int32, bool) {
 	select {
 	// when peer task without content length or total pieces count, match here
 	case <-pt.done:
-		pt.Infof("peer task done, stop get pieces from peer")
+		pt.Infof("peer task done, stop wait available peer packet")
 	case <-pt.ctx.Done():
 		pt.Debugf("context done due to %s", pt.ctx.Err())
-		if !pt.peerTaskDone {
+		if !pt.success {
 			if pt.failedCode == failedCodeNotSet {
 				pt.failedReason = reasonContextCanceled
 				pt.failedCode = dfcodes.ClientContextCanceled
@@ -528,11 +551,13 @@ func (pt *peerTask) waitAvailablePeerPacket() (int32, bool) {
 		}
 		// when schedule timeout, receivePeerPacket will close pt.peerPacketReady
 		if pt.schedulerOption.DisableAutoBackSource {
-			pt.failedReason = reasonReScheduleTimeout
-			pt.failedCode = dfcodes.ClientScheduleTimeout
-			logger.Errorf("%s, auto back source disabled", pt.failedReason)
+			pt.failedReason = reasonBackSourceDisabled
+			err := fmt.Errorf("%s, auto back source disabled", pt.failedReason)
+			pt.span.RecordError(err)
+			pt.Errorf(err.Error())
 		} else {
-			pt.Errorf("start download from source due to dfcodes.SchedNeedBackSource")
+			pt.Warnf("start download from source due to dfcodes.SchedNeedBackSource")
+			pt.span.AddEvent("back source due to scheduler says need back source ")
 			pt.needBackSource = true
 			pt.backSource()
 		}
@@ -540,11 +565,15 @@ func (pt *peerTask) waitAvailablePeerPacket() (int32, bool) {
 		if pt.schedulerOption.DisableAutoBackSource {
 			pt.failedReason = reasonReScheduleTimeout
 			pt.failedCode = dfcodes.ClientScheduleTimeout
-			logger.Errorf("%s, auto back source disabled", pt.failedReason)
+			err := fmt.Errorf("%s, auto back source disabled", pt.failedReason)
+			pt.span.RecordError(err)
+			pt.Errorf(err.Error())
+		} else {
+			pt.Warnf("start download from source due to %s", reasonReScheduleTimeout)
+			pt.span.AddEvent("back source due to schedule timeout")
+			pt.needBackSource = true
+			pt.backSource()
 		}
-		pt.Errorf("start download from source due to %s", reasonReScheduleTimeout)
-		pt.needBackSource = true
-		pt.backSource()
 	}
 	return -1, false
 }
@@ -567,7 +596,7 @@ func (pt *peerTask) dispatchPieceRequest(pieceRequestCh chan *DownloadPieceReque
 			pt.Warnf("peer task done, but still some piece request not process")
 		case <-pt.ctx.Done():
 			pt.Warnf("context done due to %s", pt.ctx.Err())
-			if !pt.peerTaskDone {
+			if !pt.success {
 				if pt.failedCode == failedCodeNotSet {
 					pt.failedReason = reasonContextCanceled
 					pt.failedCode = dfcodes.ClientContextCanceled
@@ -584,10 +613,10 @@ func (pt *peerTask) waitFailedPiece() (int32, bool) {
 	// use no default branch select to wait failed piece or exit
 	select {
 	case <-pt.done:
-		pt.Infof("peer task done, stop get pieces from peer")
+		pt.Infof("peer task done, stop wait failed piece")
 		return -1, false
 	case <-pt.ctx.Done():
-		if !pt.peerTaskDone {
+		if !pt.success {
 			if pt.failedCode == failedCodeNotSet {
 				pt.failedReason = reasonContextCanceled
 				pt.failedCode = dfcodes.ClientContextCanceled
diff --git a/client/daemon/peer/peertask_file.go b/client/daemon/peer/peertask_file.go
index c42daedb..f0f7ab48 100644
--- a/client/daemon/peer/peertask_file.go
+++ b/client/daemon/peer/peertask_file.go
@@ -37,7 +37,11 @@ import (
 
 type FilePeerTaskRequest struct {
 	scheduler.PeerTaskRequest
-	Output string
+	Output            string
+	Limit             float64
+	DisableBackSource bool
+	Pattern           string
+	Callsystem        string
 }
 
 // FilePeerTask represents a peer task to download a file
@@ -52,6 +56,11 @@ type filePeerTask struct {
 	// progressCh holds progress status
 	progressCh     chan *FilePeerTaskProgress
 	progressStopCh chan bool
+
+	// disableBackSource indicates not back source when failed
+	disableBackSource bool
+	pattern           string
+	callsystem        string
 }
 
 var _ FilePeerTask = (*filePeerTask)(nil)
@@ -75,7 +84,7 @@ type FilePeerTaskProgress struct {
 func newFilePeerTask(ctx context.Context,
 	host *scheduler.PeerHost,
 	pieceManager PieceManager,
-	request *scheduler.PeerTaskRequest,
+	request *FilePeerTaskRequest,
 	schedulerClient schedulerclient.SchedulerClient,
 	schedulerOption config.SchedulerOption,
 	perPeerRateLimit rate.Limit) (context.Context, *filePeerTask, *TinyData, error) {
@@ -89,7 +98,7 @@ func newFilePeerTask(ctx context.Context,
 	// trace register
 	regCtx, regSpan := tracer.Start(ctx, config.SpanRegisterTask)
 	logger.Infof("step 1: peer %s start to register", request.PeerId)
-	result, err := schedulerClient.RegisterPeerTask(regCtx, request)
+	result, err := schedulerClient.RegisterPeerTask(regCtx, &request.PeerTaskRequest)
 	regSpan.RecordError(err)
 	regSpan.End()
 
@@ -149,7 +158,7 @@ func newFilePeerTask(ctx context.Context,
 		}
 	}
 	logger.Infof("step 2: start report peer %s piece result", request.PeerId)
-	peerPacketStream, err := schedulerClient.ReportPieceResult(ctx, result.TaskId, request)
+	peerPacketStream, err := schedulerClient.ReportPieceResult(ctx, result.TaskId, &request.PeerTaskRequest)
 	if err != nil {
 		logger.Errorf("step 2: peer %s report piece failed: err", request.PeerId, err)
 		defer span.End()
@@ -162,12 +171,15 @@ func newFilePeerTask(ctx context.Context,
 		limiter = rate.NewLimiter(perPeerRateLimit, int(perPeerRateLimit))
 	}
 	pt := &filePeerTask{
-		progressCh:     make(chan *FilePeerTaskProgress),
-		progressStopCh: make(chan bool),
+		progressCh:        make(chan *FilePeerTaskProgress),
+		progressStopCh:    make(chan bool),
+		disableBackSource: request.DisableBackSource,
+		pattern:           request.Pattern,
+		callsystem:        request.Callsystem,
 		peerTask: peerTask{
 			host:                host,
 			needBackSource:      needBackSource,
-			request:             request,
+			request:             &request.PeerTaskRequest,
 			peerPacketStream:    peerPacketStream,
 			pieceManager:        pieceManager,
 			peerPacketReady:     make(chan bool, 1),
@@ -284,9 +296,10 @@ func (pt *filePeerTask) finish() error {
 		pt.Debugf("finish end piece result sent")
 
 		var (
-			success = true
-			code    = dfcodes.Success
-			message = "Success"
+			success      = true
+			code         = dfcodes.Success
+			message      = "Success"
+			progressDone bool
 		)
 
 		// callback to store data to output
@@ -310,7 +323,7 @@ func (pt *filePeerTask) finish() error {
 			CompletedLength: pt.completedLength.Load(),
 			PeerTaskDone:    true,
 			DoneCallback: func() {
-				pt.peerTaskDone = true
+				progressDone = true
 				close(pt.progressStopCh)
 			},
 		}
@@ -329,13 +342,14 @@ func (pt *filePeerTask) finish() error {
 		case <-pt.progressStopCh:
 			pt.Infof("progress stopped")
 		case <-pt.ctx.Done():
-			if pt.peerTaskDone {
+			if progressDone {
 				pt.Debugf("progress stopped and context done")
 			} else {
 				pt.Warnf("wait progress stopped failed, context done, but progress not stopped")
 			}
 		}
 		pt.Debugf("finished: close channel")
+		pt.success = true
 		close(pt.done)
 		pt.span.SetAttributes(config.AttributePeerTaskSuccess.Bool(true))
 		pt.span.End()
@@ -353,6 +367,7 @@ func (pt *filePeerTask) cleanUnfinished() {
 			scheduler.NewEndPieceResult(pt.taskID, pt.peerID, pt.readyPieces.Settled()))
 		pt.Debugf("clean up end piece result sent")
 
+		var progressDone bool
 		pg := &FilePeerTaskProgress{
 			State: &ProgressState{
 				Success: false,
@@ -365,7 +380,7 @@ func (pt *filePeerTask) cleanUnfinished() {
 			CompletedLength: pt.completedLength.Load(),
 			PeerTaskDone:    true,
 			DoneCallback: func() {
-				pt.peerTaskDone = true
+				progressDone = true
 				close(pt.progressStopCh)
 			},
 		}
@@ -384,7 +399,7 @@ func (pt *filePeerTask) cleanUnfinished() {
 		case <-pt.progressStopCh:
 			pt.Infof("progress stopped")
 		case <-pt.ctx.Done():
-			if pt.peerTaskDone {
+			if progressDone {
 				pt.Debugf("progress stopped and context done")
 			} else {
 				pt.Warnf("wait progress stopped failed, context done, but progress not stopped")
@@ -416,6 +431,11 @@ func (pt *filePeerTask) SetContentLength(i int64) error {
 
 func (pt *filePeerTask) backSource() {
 	defer pt.cleanUnfinished()
+	if pt.disableBackSource {
+		pt.Errorf(reasonBackSourceDisabled)
+		pt.failedReason = reasonBackSourceDisabled
+		return
+	}
 	err := pt.pieceManager.DownloadSource(pt.ctx, pt, pt.request)
 	if err != nil {
 		pt.Errorf("download from source error: %s", err)
diff --git a/client/daemon/peer/peertask_file_callback.go b/client/daemon/peer/peertask_file_callback.go
index 2434dfd3..cdd1c5a8 100644
--- a/client/daemon/peer/peertask_file_callback.go
+++ b/client/daemon/peer/peertask_file_callback.go
@@ -17,6 +17,7 @@
 package peer
 
 import (
+	"context"
 	"time"
 
 	"d7y.io/dragonfly/v2/client/daemon/storage"
@@ -91,7 +92,7 @@ func (p *filePeerTaskCallback) Done(pt Task) error {
 		return e
 	}
 	p.ptm.PeerTaskDone(p.req.PeerId)
-	err := p.pt.schedulerClient.ReportPeerResult(p.pt.ctx, &scheduler.PeerResult{
+	err := p.pt.schedulerClient.ReportPeerResult(context.Background(), &scheduler.PeerResult{
 		TaskId:         pt.GetTaskID(),
 		PeerId:         pt.GetPeerID(),
 		SrcIp:          p.ptm.host.Ip,
@@ -116,7 +117,7 @@ func (p *filePeerTaskCallback) Fail(pt Task, code base.Code, reason string) erro
 	p.ptm.PeerTaskDone(p.req.PeerId)
 	var end = time.Now()
 	pt.Log().Errorf("file peer task failed, code: %d, reason: %s", code, reason)
-	err := p.pt.schedulerClient.ReportPeerResult(p.pt.ctx, &scheduler.PeerResult{
+	err := p.pt.schedulerClient.ReportPeerResult(context.Background(), &scheduler.PeerResult{
 		TaskId:         pt.GetTaskID(),
 		PeerId:         pt.GetPeerID(),
 		SrcIp:          p.ptm.host.Ip,
diff --git a/client/daemon/peer/peertask_manager.go b/client/daemon/peer/peertask_manager.go
index 3fb75d5e..452329fa 100644
--- a/client/daemon/peer/peertask_manager.go
+++ b/client/daemon/peer/peertask_manager.go
@@ -147,8 +147,12 @@ func (ptm *peerTaskManager) StartFilePeerTask(ctx context.Context, req *FilePeer
 	}
 	// TODO ensure scheduler is ok first
 	start := time.Now()
+	limit := ptm.perPeerRateLimit
+	if req.Limit > 0 {
+		limit = rate.Limit(req.Limit)
+	}
 	ctx, pt, tiny, err := newFilePeerTask(ctx, ptm.host, ptm.pieceManager,
-		&req.PeerTaskRequest, ptm.schedulerClient, ptm.schedulerOption, ptm.perPeerRateLimit)
+		req, ptm.schedulerClient, ptm.schedulerOption, limit)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/client/daemon/peer/peertask_stream.go b/client/daemon/peer/peertask_stream.go
index 81649077..44cb55b9 100644
--- a/client/daemon/peer/peertask_stream.go
+++ b/client/daemon/peer/peertask_stream.go
@@ -49,6 +49,7 @@ type StreamPeerTask interface {
 
 type streamPeerTask struct {
 	peerTask
+	streamDone     chan struct{}
 	successPieceCh chan int32
 }
 
@@ -70,7 +71,9 @@ func newStreamPeerTask(ctx context.Context,
 	logger.Debugf("request overview, pid: %s, url: %s, filter: %s, meta: %s, tag: %s",
 		request.PeerId, request.Url, request.UrlMeta.Filter, request.UrlMeta, request.UrlMeta.Tag)
 	// trace register
-	regCtx, regSpan := tracer.Start(ctx, config.SpanRegisterTask)
+	regCtx, cancel := context.WithTimeout(ctx, schedulerOption.ScheduleTimeout.Duration)
+	defer cancel()
+	regCtx, regSpan := tracer.Start(regCtx, config.SpanRegisterTask)
 	logger.Infof("step 1: peer %s start to register", request.PeerId)
 	result, err := schedulerClient.RegisterPeerTask(regCtx, request)
 	regSpan.RecordError(err)
@@ -78,7 +81,10 @@ func newStreamPeerTask(ctx context.Context,
 
 	var needBackSource bool
 	if err != nil {
-		logger.Errorf("step 1: peer %s register failed: err", request.PeerId, err)
+		if err == context.DeadlineExceeded {
+			logger.Errorf("scheduler did not response in %s", schedulerOption.ScheduleTimeout.Duration)
+		}
+		logger.Errorf("step 1: peer %s register failed: %s", request.PeerId, err)
 		if schedulerOption.DisableAutoBackSource {
 			logger.Errorf("register peer task failed: %s, peer id: %s, auto back source disabled", err, request.PeerId)
 			span.RecordError(err)
@@ -144,6 +150,8 @@ func newStreamPeerTask(ctx context.Context,
 		limiter = rate.NewLimiter(perPeerRateLimit, int(perPeerRateLimit))
 	}
 	pt := &streamPeerTask{
+		successPieceCh: make(chan int32),
+		streamDone:     make(chan struct{}),
 		peerTask: peerTask{
 			ctx:                 ctx,
 			host:                host,
@@ -172,7 +180,6 @@ func newStreamPeerTask(ctx context.Context,
 			usedTraffic:         atomic.NewInt64(0),
 			SugaredLoggerOnWith: logger.With("peer", request.PeerId, "task", result.TaskId, "component", "streamPeerTask"),
 		},
-		successPieceCh: make(chan int32),
 	}
 	// bind func that base peer task did not implement
 	pt.backSourceFunc = pt.backSource
@@ -329,9 +336,9 @@ func (s *streamPeerTask) Start(ctx context.Context) (io.Reader, map[string]strin
 					s.Errorf("CloseWithError failed: %s", err)
 				}
 				return
-			case <-s.done:
+			case <-s.streamDone:
 				for {
-					// all data is wrote to local storage, and all data is wrote to pipe write
+					// all data wrote to local storage, and all data wrote to pipe write
 					if s.readyPieces.Settled() == desired {
 						pw.Close()
 						return
@@ -364,17 +371,19 @@ func (s *streamPeerTask) Start(ctx context.Context) (io.Reader, map[string]strin
 func (s *streamPeerTask) finish() error {
 	// send last progress
 	s.once.Do(func() {
+		s.success = true
+		// let stream return immediately
+		close(s.streamDone)
 		// send EOF piece result to scheduler
 		_ = s.peerPacketStream.Send(
 			scheduler.NewEndPieceResult(s.taskID, s.peerID, s.readyPieces.Settled()))
 		s.Debugf("end piece result sent, peer task finished")
-		close(s.done)
-		//close(s.successPieceCh)
 		if err := s.callback.Done(s); err != nil {
 			s.span.RecordError(err)
 			s.Errorf("done callback error: %s", err)
 		}
 		s.span.SetAttributes(config.AttributePeerTaskSuccess.Bool(true))
+		close(s.done)
 	})
 	return nil
 }
@@ -386,6 +395,7 @@ func (s *streamPeerTask) cleanUnfinished() {
 		_ = s.peerPacketStream.Send(
 			scheduler.NewEndPieceResult(s.taskID, s.peerID, s.readyPieces.Settled()))
 		s.Errorf("end piece result sent, peer task failed")
+		close(s.streamDone)
 		close(s.done)
 		//close(s.successPieceCh)
 		if err := s.callback.Fail(s, s.failedCode, s.failedReason); err != nil {
diff --git a/client/daemon/peer/peertask_stream_callback.go b/client/daemon/peer/peertask_stream_callback.go
index dd2cd031..917de1f3 100644
--- a/client/daemon/peer/peertask_stream_callback.go
+++ b/client/daemon/peer/peertask_stream_callback.go
@@ -17,6 +17,7 @@
 package peer
 
 import (
+	"context"
 	"time"
 
 	"d7y.io/dragonfly/v2/client/daemon/storage"
@@ -89,7 +90,7 @@ func (p *streamPeerTaskCallback) Done(pt Task) error {
 		return e
 	}
 	p.ptm.PeerTaskDone(p.req.PeerId)
-	err := p.pt.schedulerClient.ReportPeerResult(p.pt.ctx, &scheduler.PeerResult{
+	err := p.pt.schedulerClient.ReportPeerResult(context.Background(), &scheduler.PeerResult{
 		TaskId:         pt.GetTaskID(),
 		PeerId:         pt.GetPeerID(),
 		SrcIp:          p.ptm.host.Ip,
@@ -114,7 +115,7 @@ func (p *streamPeerTaskCallback) Fail(pt Task, code base.Code, reason string) er
 	p.ptm.PeerTaskDone(p.req.PeerId)
 	var end = time.Now()
 	pt.Log().Errorf("stream peer task failed, code: %d, reason: %s", code, reason)
-	err := p.pt.schedulerClient.ReportPeerResult(p.pt.ctx, &scheduler.PeerResult{
+	err := p.pt.schedulerClient.ReportPeerResult(context.Background(), &scheduler.PeerResult{
 		TaskId:         pt.GetTaskID(),
 		PeerId:         pt.GetPeerID(),
 		SrcIp:          p.ptm.host.Ip,
diff --git a/client/daemon/rpcserver/rpcserver.go b/client/daemon/rpcserver/rpcserver.go
index 81c8e792..ffbe2dd1 100644
--- a/client/daemon/rpcserver/rpcserver.go
+++ b/client/daemon/rpcserver/rpcserver.go
@@ -144,7 +144,11 @@ func (m *server) Download(ctx context.Context,
 			PeerId:   clientutil.GenPeerID(m.peerHost),
 			PeerHost: m.peerHost,
 		},
-		Output: req.Output,
+		Output:            req.Output,
+		Limit:             req.Limit,
+		DisableBackSource: req.DisableBackSource,
+		Pattern:           req.Pattern,
+		Callsystem:        req.Callsystem,
 	}
 	log := logger.With("peer", peerTask.PeerId, "component", "downloadService")
 
diff --git a/deploy/helm-charts b/deploy/helm-charts
index 7aa52a9e..b585828c 160000
--- a/deploy/helm-charts
+++ b/deploy/helm-charts
@@ -1,1 +1,1 @@
-Subproject commit 7aa52a9e053971b232b3c2f5a6a06fc56c4e8e88
+Subproject commit b585828c390fe0d9e341005bb9a858c6c21b35cc
diff --git a/go.mod b/go.mod
index e6b8b26d..ff7fea47 100644
--- a/go.mod
+++ b/go.mod
@@ -67,6 +67,7 @@ require (
 	go.uber.org/atomic v1.6.0
 	go.uber.org/zap v1.16.0
 	golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
+	golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
 	golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
diff --git a/go.sum b/go.sum
index 2adacb99..ec9f21f6 100644
--- a/go.sum
+++ b/go.sum
@@ -977,8 +977,9 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c h1:HiAZXo96zOhVhtFHchj/ojzoxCFiPrp9/j0GtS38V3g=
 golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a h1:4Kd8OPUx1xgUwrHDaviWZO8MsgoZTZYC3g+8m16RBww=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
diff --git a/manager/auth/oauth/github.go b/manager/auth/oauth/github.go
new file mode 100644
index 00000000..cdd46709
--- /dev/null
+++ b/manager/auth/oauth/github.go
@@ -0,0 +1,59 @@
+package oauth
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/github"
+	"gorm.io/gorm"
+)
+
+type githubOauth2 struct {
+	baseOauth2
+}
+
+func NewGithubOauth2(name string, clientID string, clientSecret string, db *gorm.DB) (Oauther, error) {
+
+	oa := &githubOauth2{}
+	oa.Name = name
+	oa.Config = &oauth2.Config{
+		ClientID:     clientID,
+		ClientSecret: clientSecret,
+		Scopes:       strings.Split(GithubScopes, ","),
+		Endpoint:     github.Endpoint,
+	}
+	oa.UserInfoURL = GithubUserInfoURL
+
+	redirectURL, err := oa.GetRediectURL(db)
+	if err != nil {
+		return nil, err
+	}
+	oa.Config.RedirectURL = redirectURL
+	return oa, nil
+}
+
+func (oa *githubOauth2) GetOauthUserInfo(token string) (*oauth2User, error) {
+	req, err := http.NewRequest("GET", oa.UserInfoURL, nil)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Authorization", "token"+" "+token)
+	response, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer response.Body.Close()
+	contents, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return nil, err
+	}
+	u := oauth2User{}
+	err = json.Unmarshal(contents, &u)
+	if err != nil {
+		return nil, err
+	}
+	return &u, nil
+}
diff --git a/manager/auth/oauth/google.go b/manager/auth/oauth/google.go
new file mode 100644
index 00000000..90bd61a3
--- /dev/null
+++ b/manager/auth/oauth/google.go
@@ -0,0 +1,33 @@
+package oauth
+
+import (
+	"strings"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	"gorm.io/gorm"
+)
+
+type googleOauth2 struct {
+	baseOauth2
+}
+
+func NewGoogleOauth2(name string, clientID string, clientSecret string, db *gorm.DB) (Oauther, error) {
+
+	oa := &googleOauth2{}
+	oa.Name = name
+	oa.Config = &oauth2.Config{
+		ClientID:     clientID,
+		ClientSecret: clientSecret,
+		Scopes:       strings.Split(GoogleScopes, ","),
+		Endpoint:     google.Endpoint,
+	}
+	oa.UserInfoURL = GithubUserInfoURL
+
+	redirectURL, err := oa.GetRediectURL(db)
+	if err != nil {
+		return nil, err
+	}
+	oa.Config.RedirectURL = redirectURL
+	return oa, nil
+}
diff --git a/manager/auth/oauth/oauth.go b/manager/auth/oauth/oauth.go
new file mode 100644
index 00000000..9eb38b43
--- /dev/null
+++ b/manager/auth/oauth/oauth.go
@@ -0,0 +1,164 @@
+package oauth
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"golang.org/x/crypto/bcrypt"
+	"golang.org/x/oauth2"
+	"gorm.io/gorm"
+)
+
+const (
+	Google            = "google"
+	Github            = "github"
+	GoogleScopes      = "https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile"
+	GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v2/userinfo"
+
+	GithubScopes      = "user,public_repo"
+	GithubUserInfoURL = "https://api.github.com/user"
+)
+
+type baseOauth2 struct {
+	Name        string
+	UserInfoURL string
+	Config      *oauth2.Config
+}
+
+type oauth2User struct {
+	Name  string `json:"name"`
+	Email string `json:"email"`
+}
+
+// oauth interface
+type Oauther interface {
+	GetRediectURL(*gorm.DB) (string, error)
+	GetOauthUserInfo(string) (*oauth2User, error)
+	ExchangeTokenByCode(string) (string, error)
+	OauthLinkUser(*oauth2User, *gorm.DB) (*model.User, error)
+	AuthCodeURL() string
+}
+
+func New(oauth *model.Oauth, db *gorm.DB) (Oauther, error) {
+	var o Oauther
+	var err error
+	switch oauth.Name {
+	case Google:
+		o, err = NewGoogleOauth2(oauth.Name, oauth.ClientID, oauth.ClientSecret, db)
+	case Github:
+		o, err = NewGithubOauth2(oauth.Name, oauth.ClientID, oauth.ClientSecret, db)
+	default:
+		o, err = NewBaseOauth2(oauth.Name, oauth.ClientID, oauth.ClientSecret, oauth.Scopes, oauth.AuthURL, oauth.TokenURL, db)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return o, nil
+
+}
+
+func NewBaseOauth2(name string, clientID string, clientSecret string, scopes string, authURL string, tokenURL string, db *gorm.DB) (Oauther, error) {
+
+	oa := &baseOauth2{
+		Name: name,
+		Config: &oauth2.Config{
+			ClientID:     clientID,
+			ClientSecret: clientSecret,
+			Scopes:       strings.Split(scopes, ","),
+			Endpoint: oauth2.Endpoint{
+				AuthURL:  authURL,
+				TokenURL: tokenURL,
+			},
+		},
+	}
+	redirectURL, err := oa.GetRediectURL(db)
+	if err != nil {
+		return nil, err
+	}
+	oa.Config.RedirectURL = redirectURL
+	return oa, nil
+}
+
+func (oa *baseOauth2) GetRediectURL(db *gorm.DB) (string, error) {
+
+	s := model.Settings{}
+	if err := db.First(&s, model.Settings{
+		Key: "server_domain",
+	}).Error; err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%s/api/v1/oauth/callback/%s", s.Value, oa.Name), nil
+}
+
+func (oa *baseOauth2) AuthCodeURL() string {
+	return oa.Config.AuthCodeURL(stringutils.RandString(5))
+}
+
+func (oa *baseOauth2) GetOauthUserInfo(token string) (*oauth2User, error) {
+	response, err := http.Get(fmt.Sprintf("%s?access_token=%s", oa.UserInfoURL, token))
+	if err != nil {
+		return nil, err
+	}
+	defer response.Body.Close()
+	contents, err := ioutil.ReadAll(response.Body)
+	if err != nil {
+		return nil, err
+	}
+	u := oauth2User{}
+	err = json.Unmarshal(contents, &u)
+	if err != nil {
+		return nil, err
+	}
+	return &u, nil
+}
+
+func (oa *baseOauth2) ExchangeTokenByCode(code string) (string, error) {
+	token, err := oa.Config.Exchange(context.Background(), code)
+	if err != nil {
+		return "", err
+	}
+	if oa.UserInfoURL == "" {
+		return "", errors.New("UserInfoURL is empty")
+	}
+	return token.AccessToken, nil
+}
+
+func (oa *baseOauth2) OauthLinkUser(u *oauth2User, db *gorm.DB) (*model.User, error) {
+	if u.Name == "admin" {
+		return nil, errors.New("admin is not allowed to login by oauth")
+	}
+	encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("Dragonfly2"), bcrypt.MinCost)
+	if err != nil {
+		return nil, err
+	}
+	var userCount int64
+	if err := db.Model(model.User{}).Where("name = ?", u.Name).Count(&userCount).Error; err != nil {
+		return nil, err
+	}
+	if userCount <= 0 {
+		user := model.User{
+			EncryptedPassword: string(encryptedPasswordBytes),
+			Name:              u.Name,
+			Email:             u.Email,
+		}
+
+		if err := db.Create(&user).Error; err != nil {
+			return nil, err
+		}
+		return &user, nil
+
+	}
+	user := model.User{}
+	if err := db.Model(model.User{}).Where("name = ?", u.Name).First(&user).Error; err != nil {
+		return nil, err
+	}
+	return &user, nil
+
+}
diff --git a/manager/database/database.go b/manager/database/database.go
index 2e83c759..1b7be26b 100644
--- a/manager/database/database.go
+++ b/manager/database/database.go
@@ -101,6 +101,8 @@ func migrate(db *gorm.DB) error {
 		&model.Scheduler{},
 		&model.SecurityGroup{},
 		&model.User{},
+		&model.Settings{},
+		&model.Oauth{},
 	)
 }
 
@@ -156,20 +158,21 @@ func seed(db *gorm.DB) error {
 		}
 	}
 
-	var adminUserCount int64
-	var adminUserName = "admin"
-	if err := db.Model(model.User{}).Where("name = ?", adminUserName).Count(&adminUserCount).Error; err != nil {
+	var rootUserCount int64
+	if err := db.Model(model.User{}).Count(&rootUserCount).Error; err != nil {
 		return err
 	}
-	if adminUserCount <= 0 {
-		encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("Dragonfly2"), bcrypt.MinCost)
+	if rootUserCount <= 0 {
+		encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte("dragonfly"), bcrypt.MinCost)
 		if err != nil {
 			return err
 		}
 		if err := db.Create(&model.User{
+			Model: model.Model{
+				ID: uint(1),
+			},
 			EncryptedPassword: string(encryptedPasswordBytes),
-			Name:              adminUserName,
-			Email:             fmt.Sprintf("%s@Dragonfly2.com", adminUserName),
+			Name:              "root",
 			State:             model.UserStateEnabled,
 		}).Error; err != nil {
 			return err
diff --git a/manager/handlers/oauth.go b/manager/handlers/oauth.go
new file mode 100644
index 00000000..2cb40449
--- /dev/null
+++ b/manager/handlers/oauth.go
@@ -0,0 +1,211 @@
+package handlers
+
+import (
+	"fmt"
+	"net/http"
+
+	"d7y.io/dragonfly/v2/manager/types"
+	jwt "github.com/appleboy/gin-jwt/v2"
+	"github.com/gin-gonic/gin"
+)
+
+// @Summary Create Oauth
+// @Description create by json config
+// @Tags Oauth
+// @Accept json
+// @Produce json
+// @Param Oauth body types.CreateOauthRequest true "Oauth"
+// @Success 200 {object} model.Oauth
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /oauths [post]
+func (h *Handlers) CreateOauth(ctx *gin.Context) {
+	var json types.CreateOauthRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	oauth, err := h.Service.CreateOauth(json)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, oauth)
+}
+
+// @Summary Destroy Oauth
+// @Description Destroy by id
+// @Tags Oauth
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /oauths/{id} [delete]
+func (h *Handlers) DestroyOauth(ctx *gin.Context) {
+	var params types.OauthParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	err := h.Service.DestroyOauth(params.ID)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary start oauth signin
+// @Description start oauth signin
+// @Tags Oauth
+// @Accept json
+// @Produce json
+// @Param oauth_name path string true "oauth_name"
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /signin/{oauth_name} [get]
+func (h *Handlers) OauthSignin(ctx *gin.Context) {
+	var params types.OauthPathParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	oauthURL, err := h.Service.OauthSignin(params.OauthName)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+	fmt.Println(oauthURL)
+	ctx.Redirect(http.StatusMovedPermanently, oauthURL)
+}
+
+// @Summary start oauth callback
+// @Description start oauth callback
+// @Tags Oauth
+// @Param oauth_name path string true "oauth_name"
+// @Param code query string true "code"
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /callback/{oauth_name} [get]
+func (h *Handlers) OauthCallback(j *jwt.GinJWTMiddleware) func(*gin.Context) {
+	return func(ctx *gin.Context) {
+		var params types.OauthPathParams
+		if err := ctx.ShouldBindUri(&params); err != nil {
+			ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+			return
+		}
+
+		code := ctx.Query("code")
+		if code == "" {
+			ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": "code is required"})
+			return
+		}
+
+		user, err := h.Service.OauthCallback(params.OauthName, code)
+		if err != nil {
+			ctx.Error(err)
+			return
+		}
+		jwtToken, _, err := j.TokenGenerator(user)
+		if err != nil {
+			ctx.Error(err)
+			return
+		}
+		ctx.SetCookie("jwt", jwtToken, 3600, "", "", false, true)
+		ctx.Redirect(http.StatusMovedPermanently, "/")
+	}
+}
+
+// @Summary Update Oauth
+// @Description Update by json config
+// @Tags Oauth
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Param Oauth body types.UpdateOauthRequest true "Oauth"
+// @Success 200 {object} model.Oauth
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /oauth/{id} [patch]
+func (h *Handlers) UpdateOauth(ctx *gin.Context) {
+	var params types.OauthParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	var json types.UpdateOauthRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	oauth, err := h.Service.UpdateOauth(params.ID, json)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, oauth)
+}
+
+// @Summary Get Oauth
+// @Description Get Oauth by id
+// @Tags Oauth
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Success 200 {object} model.Oauth
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /oauth/{id} [get]
+func (h *Handlers) GetOauth(ctx *gin.Context) {
+	var params types.OauthParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	oauth, err := h.Service.GetOauth(params.ID)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, oauth)
+}
+
+// @Summary Get Oauths
+// @Description Get Oauths
+// @Tags Oauth
+// @Accept json
+// @Produce json
+// @Success 200 {object} []model.Oauth
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /oauth [get]
+func (h *Handlers) GetOauths(ctx *gin.Context) {
+	oauths, err := h.Service.GetOauths()
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, oauths)
+}
diff --git a/manager/handlers/permission.go b/manager/handlers/permission.go
index d65a9ee8..5cd34a22 100644
--- a/manager/handlers/permission.go
+++ b/manager/handlers/permission.go
@@ -19,166 +19,19 @@ package handlers
 import (
 	"net/http"
 
-	"d7y.io/dragonfly/v2/manager/types"
 	"github.com/gin-gonic/gin"
 )
 
 // @Summary Get Permissions
 // @Description Get Permissions
-// @Tags permission
+// @Tags Permission
 // @Produce json
-// @Success 200 {object} RoutesInfo
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
+// @Success 200 {object} []rbac.Permission
+// @Failure 400
+// @Failure 500
 // @Router /permissions [get]
-
 func (h *Handlers) GetPermissions(g *gin.Engine) func(ctx *gin.Context) {
 	return func(ctx *gin.Context) {
-
-		permissionGroups := h.Service.GetPermissions(g)
-
-		ctx.JSON(http.StatusOK, permissionGroups)
-	}
-}
-
-// @Summary Create Role
-// @Description Create Role by json config
-// @Tags role
-// @Accept json
-// @Produce json
-// @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /roles [post]
-
-func (h *Handlers) CreateRole(ctx *gin.Context) {
-	var json types.CreateRolePermissionRequest
-	if err := ctx.ShouldBindJSON(&json); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	err := h.Service.CreateRole(json)
-	if err != nil {
-		ctx.Error(err)
-		return
-	}
-	ctx.Status(http.StatusOK)
-}
-
-// @Summary Update Role
-// @Description Remove Role Permission by json config
-// @Tags role
-// @Accept json
-// @Produce json
-// @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /roles/:role_name/permission [delete]
-
-func (h *Handlers) RemoveRolePermission(ctx *gin.Context) {
-
-	var params types.RoleParams
-	if err := ctx.ShouldBindUri(&params); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	var json types.ObjectPermission
-	if err := ctx.ShouldBindJSON(&json); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	err := h.Service.RemoveRolePermission(params.RoleName, json)
-	if err != nil {
-		ctx.Error(err)
-		return
-	}
-	ctx.Status(http.StatusOK)
-}
-
-// @Summary Update Role
-// @Description Add Role Permission by json config
-// @Tags role
-// @Accept json
-// @Produce json
-// @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /roles/:role_name/permission [post]
-
-func (h *Handlers) AddRolePermission(ctx *gin.Context) {
-
-	var params types.RoleParams
-	if err := ctx.ShouldBindUri(&params); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	var json types.ObjectPermission
-	if err := ctx.ShouldBindJSON(&json); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	err := h.Service.AddRolePermission(params.RoleName, json)
-	if err != nil {
-		ctx.Error(err)
-		return
-	}
-	ctx.Status(http.StatusOK)
-}
-
-// @Summary Get Roles
-// @Description Get Roles by name
-// @Tags role
-// @Accept text
-// @Produce json
-// @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /roles [get]
-
-func (h *Handlers) GetRoles(ctx *gin.Context) {
-	roles := h.Service.GetRoles()
-	ctx.JSON(http.StatusOK, roles)
-}
-
-// @Summary Get Role
-// @Description Get Role
-// @Tags permission
-// @Accept text
-// @Produce json
-// @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /roles/:role_name [get]
-
-func (h *Handlers) GetRole(ctx *gin.Context) {
-	var params types.RoleParams
-	if err := ctx.ShouldBindUri(&params); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	ctx.JSON(http.StatusOK, h.Service.GetRole(params.RoleName))
-}
-
-// @Summary Destroy Permission
-// @Description Destroy Permission by json config
-// @Tags permission
-// @Accept json
-// @Produce json
-// @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /role/:role_name [delete]
-
-func (h *Handlers) DestroyRole(ctx *gin.Context) {
-	var params types.RoleParams
-	if err := ctx.ShouldBindUri(&params); err != nil {
-		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
-		return
-	}
-	err := h.Service.DestroyRole(params.RoleName)
-	if err != nil {
-		ctx.Error(err)
-		return
+		ctx.JSON(http.StatusOK, h.Service.GetPermissions(g))
 	}
-	ctx.Status(http.StatusOK)
 }
diff --git a/manager/handlers/role.go b/manager/handlers/role.go
new file mode 100644
index 00000000..fb9e27c4
--- /dev/null
+++ b/manager/handlers/role.go
@@ -0,0 +1,181 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package handlers
+
+import (
+	"net/http"
+
+	"d7y.io/dragonfly/v2/manager/types"
+	"github.com/gin-gonic/gin"
+)
+
+// @Summary Create Role
+// @Description Create Role by json config
+// @Tags Role
+// @Accept json
+// @Produce json
+// @Param Role body types.CreateRoleRequest true "Role"
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /roles [post]
+func (h *Handlers) CreateRole(ctx *gin.Context) {
+	var json types.CreateRoleRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	if err := h.Service.CreateRole(json); err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Destroy Role
+// @Description Destroy role by json config
+// @Tags Role
+// @Accept json
+// @Produce json
+// @Param role path string true "role"
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /roles/:role [delete]
+func (h *Handlers) DestroyRole(ctx *gin.Context) {
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	if ok, err := h.Service.DestroyRole(params.Role); err != nil {
+		ctx.Error(err)
+		return
+	} else if !ok {
+		ctx.Status(http.StatusNotFound)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Get Role
+// @Description Get Role
+// @Tags Role
+// @Accept json
+// @Produce json
+// @Param role path string true "role"
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /roles/:role [get]
+func (h *Handlers) GetRole(ctx *gin.Context) {
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	ctx.JSON(http.StatusOK, h.Service.GetRole(params.Role))
+}
+
+// @Summary Get Roles
+// @Description Get roles
+// @Tags Role
+// @Accept json
+// @Produce json
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /roles [get]
+func (h *Handlers) GetRoles(ctx *gin.Context) {
+	roles := h.Service.GetRoles()
+	ctx.JSON(http.StatusOK, roles)
+}
+
+// @Summary Add Permission For Role
+// @Description Add Permission by json config
+// @Tags Role
+// @Accept json
+// @Produce json
+// @Param Permission body types.AddPermissionForRoleRequest true "Permission"
+// @Param role path string true "role"
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /roles/:role/permissions [post]
+func (h *Handlers) AddPermissionForRole(ctx *gin.Context) {
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	var json types.AddPermissionForRoleRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	if ok, err := h.Service.AddPermissionForRole(params.Role, json); err != nil {
+		ctx.Error(err)
+		return
+	} else if !ok {
+		ctx.Status(http.StatusConflict)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Update Role
+// @Description Remove Role Permission by json config
+// @Tags Role
+// @Accept json
+// @Produce json
+// @Param Permission body types.DeletePermissionForRoleRequest true "Permission"
+// @Param role path string true "role"
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /roles/:role/permissions [delete]
+func (h *Handlers) DeletePermissionForRole(ctx *gin.Context) {
+	var params types.RoleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	var json types.DeletePermissionForRoleRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	if ok, err := h.Service.DeletePermissionForRole(params.Role, json); err != nil {
+		ctx.Error(err)
+		return
+	} else if !ok {
+		ctx.Status(http.StatusNotFound)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
diff --git a/manager/handlers/settings.go b/manager/handlers/settings.go
new file mode 100644
index 00000000..3db7f10c
--- /dev/null
+++ b/manager/handlers/settings.go
@@ -0,0 +1,132 @@
+package handlers
+
+import (
+	"net/http"
+
+	"d7y.io/dragonfly/v2/manager/types"
+	"github.com/gin-gonic/gin"
+)
+
+// @Summary Create Setting
+// @Description create by json config
+// @Tags Setting
+// @Accept json
+// @Produce json
+// @Param Setting body types.CreateSettingRequest true "Setting"
+// @Success 200 {object} model.Setting
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /settings [post]
+func (h *Handlers) CreateSetting(ctx *gin.Context) {
+	var json types.CreateSettingRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	setting, err := h.Service.CreateSetting(json)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, setting)
+}
+
+// @Summary Destroy Setting
+// @Description Destroy by key
+// @Tags Setting
+// @Accept json
+// @Produce json
+// @Param id path string true "key"
+// @Success 200
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /settings/{id} [delete]
+func (h *Handlers) DestroySetting(ctx *gin.Context) {
+	var params types.SettingParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	err := h.Service.DestroySetting(params.Key)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Update Setting
+// @Description Update by json config
+// @Tags Setting
+// @Accept json
+// @Produce json
+// @Param key path string true "key"
+// @Param Setting body types.UpdateSettingRequest true "Setting"
+// @Success 200 {object} model.Setting
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /settings/{id} [patch]
+func (h *Handlers) UpdateSetting(ctx *gin.Context) {
+
+	var params types.SettingParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.Error(err)
+		return
+	}
+	var json types.UpdateSettingRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	setting, err := h.Service.UpdateSetting(params.Key, json)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.JSON(http.StatusOK, setting)
+}
+
+// @Summary Get Settings
+// @Description Get Settings
+// @Tags Setting
+// @Accept json
+// @Produce json
+// @Param page query int true "current page" default(0)
+// @Param per_page query int true "return max item count, default 10, max 50" default(10) minimum(2) maximum(50)
+// @Success 200 {object} []model.Setting
+// @Failure 400 {object} HTTPError
+// @Failure 404 {object} HTTPError
+// @Failure 500 {object} HTTPError
+// @Router /settings [get]
+func (h *Handlers) GetSettings(ctx *gin.Context) {
+	var query types.GetSettingsQuery
+	if err := ctx.ShouldBindQuery(&query); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	h.setPaginationDefault(&query.Page, &query.PerPage)
+	settings, err := h.Service.GetSettings(query)
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	totalCount, err := h.Service.SettingTotalCount()
+	if err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	h.setPaginationLinkHeader(ctx, query.Page, query.PerPage, int(totalCount))
+	ctx.JSON(http.StatusOK, settings)
+}
diff --git a/manager/handlers/user.go b/manager/handlers/user.go
index a36076af..fce32dfc 100644
--- a/manager/handlers/user.go
+++ b/manager/handlers/user.go
@@ -24,7 +24,7 @@ import (
 )
 
 // @Summary SignUp user
-// @Description SignUp user by json config
+// @Description signup by json config
 // @Tags User
 // @Accept json
 // @Produce json
@@ -49,74 +49,116 @@ func (h *Handlers) SignUp(ctx *gin.Context) {
 	ctx.JSON(http.StatusOK, user)
 }
 
-// @Summary Delete Role For User
-// @Description Delete Role For User by uri config
-// @Tags users
-// @Accept text
+// @Summary Reset Password For User
+// @Description reset password by json config
+// @Tags User
+// @Accept json
 // @Produce json
+// @Param User body types.ResetPasswordRequest true "User"
 // @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /users/:id/roles/:role_name [delete]
+// @Failure 400
+// @Failure 500
+// @Router /users/:id/reset_password [post]
+func (h *Handlers) ResetPassword(ctx *gin.Context) {
+	var params types.UserParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
 
-func (h *Handlers) DeleteRoleForUser(ctx *gin.Context) {
-	var params types.RoleRequest
+	var json types.ResetPasswordRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	if err := h.Service.ResetPassword(params.ID, json); err != nil {
+		ctx.Error(err)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Get User Roles
+// @Description get roles by json config
+// @Tags User
+// @Produce json
+// @Param id path string true "id"
+// @Success 200 {object} []string
+// @Failure 400
+// @Failure 500
+// @Router /users/:id/roles [get]
+func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
+	var params types.UserParams
 	if err := ctx.ShouldBindUri(&params); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	err := h.Service.DeleteRoleForUser(params.ID, params.RoleName)
+
+	roles, err := h.Service.GetRolesForUser(params.ID)
 	if err != nil {
 		ctx.Error(err)
 		return
 	}
-	ctx.Status(http.StatusOK)
+
+	ctx.JSON(http.StatusOK, roles)
 }
 
 // @Summary Add Role For User
-// @Description Add Role For User by uri config
-// @Tags users
-// @Accept text
+// @Description add role to user by uri config
+// @Tags Users
+// @Accept json
 // @Produce json
+// @Param id path string true "id"
+// @Param role path string true "role"
 // @Success 200
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /users/:id/roles/:role_name [post]
-
+// @Failure 400
+// @Failure 500
+// @Router /users/:id/roles/:role [put]
 func (h *Handlers) AddRoleToUser(ctx *gin.Context) {
-	var params types.RoleRequest
+	var params types.AddRoleForUserParams
 	if err := ctx.ShouldBindUri(&params); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	err := h.Service.AddRoleForUser(params.ID, params.RoleName)
-	if err != nil {
+
+	if ok, err := h.Service.AddRoleForUser(params); err != nil {
 		ctx.Error(err)
 		return
+	} else if !ok {
+		ctx.Status(http.StatusConflict)
+		return
 	}
+
 	ctx.Status(http.StatusOK)
 }
 
-// @Summary Get User Roles
-// @Description Get User Roles
-// @Tags User
+// @Summary Delete Role For User
+// @Description delete role by uri config
+// @Tags Users
+// @Accept json
 // @Produce json
-// @Success 200 {object} RoutesInfo
-// @Failure 400 {object} HTTPError
-// @Failure 500 {object} HTTPError
-// @Router /users/:id/roles [get]
-
-func (h *Handlers) GetRolesForUser(ctx *gin.Context) {
-	var params types.UserParams
+// @Param id path string true "id"
+// @Param role path string true "role"
+// @Success 200
+// @Failure 400
+// @Failure 500
+// @Router /users/:id/roles/:role [delete]
+func (h *Handlers) DeleteRoleForUser(ctx *gin.Context) {
+	var params types.DeleteRoleForUserParams
 	if err := ctx.ShouldBindUri(&params); err != nil {
 		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
 		return
 	}
-	roles, err := h.Service.GetRolesForUser(params.ID, ctx.GetString("userName"))
-	if err != nil {
+
+	if ok, err := h.Service.DeleteRoleForUser(params); err != nil {
 		ctx.Error(err)
 		return
+	} else if !ok {
+		ctx.Status(http.StatusNotFound)
+		return
 	}
-	ctx.JSON(http.StatusOK, roles)
 
+	ctx.Status(http.StatusOK)
 }
diff --git a/manager/manager.go b/manager/manager.go
index e02c1a10..62d32115 100644
--- a/manager/manager.go
+++ b/manager/manager.go
@@ -93,7 +93,7 @@ func New(cfg *config.Config) (*Server, error) {
 	}
 
 	// Initialize roles and check roles
-	err = rbac.InitRole(enforcer, router)
+	err = rbac.InitRBAC(enforcer, router)
 	if err != nil {
 		return nil, err
 	}
diff --git a/manager/middlewares/jwt.go b/manager/middlewares/jwt.go
index 3a756e77..8302cf3c 100644
--- a/manager/middlewares/jwt.go
+++ b/manager/middlewares/jwt.go
@@ -28,12 +28,13 @@ import (
 )
 
 type user struct {
-	userName string
-	ID       uint
+	name string
+	id   uint
 }
 
 func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
-	var identityKey = "username"
+	identityKey := "id"
+
 	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
 		Realm:       "Dragonfly",
 		Key:         []byte("Secret Key"),
@@ -43,34 +44,38 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 
 		IdentityHandler: func(c *gin.Context) interface{} {
 			claims := jwt.ExtractClaims(c)
-			userName, ok := claims[identityKey]
+
+			id, ok := claims[identityKey]
 			if !ok {
 				c.JSON(http.StatusUnauthorized, gin.H{
-					"message": "Unavailable token: require username info",
+					"message": "Unavailable token: require user name",
 				})
 				c.Abort()
 				return nil
 			}
-			userID, ok := claims["ID"]
+
+			name, ok := claims["name"]
 			if !ok {
 				c.JSON(http.StatusUnauthorized, gin.H{
-					"message": "Unavailable token: require id info",
+					"message": "Unavailable token: require user id",
 				})
 				c.Abort()
 				return nil
 			}
+
 			u := &user{
-				userName: userName.(string),
-				ID:       uint(userID.(float64)),
+				name: name.(string),
+				id:   id.(uint),
 			}
-			c.Set("userName", u.userName)
-			c.Set("userID", u.ID)
+
+			c.Set("name", u.name)
+			c.Set("id", u.id)
 			return u
 		},
 
 		Authenticator: func(c *gin.Context) (interface{}, error) {
 			var json types.SignInRequest
-			if err := c.ShouldBind(&json); err != nil {
+			if err := c.ShouldBindJSON(&json); err != nil {
 				return "", jwt.ErrMissingLoginValues
 			}
 
@@ -85,10 +90,11 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 		PayloadFunc: func(data interface{}) jwt.MapClaims {
 			if u, ok := data.(*model.User); ok {
 				return jwt.MapClaims{
-					identityKey: u.Name,
-					"ID":        u.ID,
+					identityKey: u.ID,
+					"name":      u.Name,
 				}
 			}
+
 			return jwt.MapClaims{}
 		},
 
@@ -116,7 +122,7 @@ func Jwt(service service.REST) (*jwt.GinJWTMiddleware, error) {
 			})
 		},
 
-		TokenLookup:    "header: Authorization, query: token, cookie: jwt",
+		TokenLookup:    "header: Authorization, cookie: jwt, query: token",
 		TokenHeadName:  "Bearer",
 		TimeFunc:       time.Now,
 		SendCookie:     true,
diff --git a/manager/middlewares/rbac.go b/manager/middlewares/rbac.go
index 5c47bbc0..d1543fce 100644
--- a/manager/middlewares/rbac.go
+++ b/manager/middlewares/rbac.go
@@ -17,6 +17,7 @@
 package middlewares
 
 import (
+	"fmt"
 	"net/http"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
@@ -27,47 +28,35 @@ import (
 
 func RBAC(e *casbin.Enforcer) gin.HandlerFunc {
 	return func(c *gin.Context) {
-		userName := c.GetString("userName")
-		// request path
-		p := c.Request.URL.Path
-		permissionName, err := rbac.GetAPIGroupName(p)
-		if err != nil {
-			c.Next()
-			return
-		}
-		// request method
-		m := c.Request.Method
-		action := rbac.HTTPMethodToAction(m)
-		// rbac validation
-		adminRes, err := e.HasRoleForUser(userName, "admin")
+		action := rbac.HTTPMethodToAction(c.Request.Method)
+		permission, err := rbac.GetAPIGroupName(c.Request.URL.Path)
 		if err != nil {
+			logger.Errorf("get api group name error: %s", err)
 			c.JSON(http.StatusUnauthorized, gin.H{
-				"message": "permission validate error",
+				"message": "permission validate error!",
 			})
 			c.Abort()
 			return
 		}
-		if adminRes {
-			c.Next()
-			return
-		}
-		res, err := e.Enforce(userName, permissionName, action)
+
+		ok, err := e.Enforce(fmt.Sprint(c.GetUint("id")), permission, action)
 		if err != nil {
 			logger.Errorf("RBAC validate error: %s", err)
 			c.JSON(http.StatusUnauthorized, gin.H{
-				"message": "permission validate error, please see log!",
+				"message": "permission validate error!",
 			})
 			c.Abort()
 			return
 		}
-		if !res {
+
+		if !ok {
 			c.JSON(http.StatusUnauthorized, gin.H{
 				"message": "permission deny",
 			})
 			c.Abort()
 			return
 		}
-		c.Next()
 
+		c.Next()
 	}
 }
diff --git a/manager/model/oauth.go b/manager/model/oauth.go
new file mode 100644
index 00000000..06c7d5b4
--- /dev/null
+++ b/manager/model/oauth.go
@@ -0,0 +1,13 @@
+package model
+
+type Oauth struct {
+	Model
+	Name         string `gorm:"column:name;type:varchar(256);index:uk_oauth_name,unique;not null;comment:oauth name" json:"name"`
+	ClientID     string `gorm:"column:client_id;type:varchar(256);index:uk_oauth_client_id,unique;not null;comment:client id for oauth" json:"client_id"`
+	ClientSecret string `gorm:"column:client_secret;type:varchar(1024);comment:client secret for oauth" json:"client_secret"`
+	// scope list split by ','
+	Scopes      string `gorm:"column:scopes;type:varchar(1024);comment:scopes" json:"scopes"`
+	UserInfoURL string `gorm:"column:user_info_url;type:varchar(256);not null;comment:user info url" json:"user_info_url"`
+	AuthURL     string `gorm:"column:auth_url;type:varchar(256);not null;comment:auth url" json:"auth_url"`
+	TokenURL    string `gorm:"column:token_url;type:varchar(256);not null;comment:token url" json:"token_url"`
+}
diff --git a/manager/model/settings.go b/manager/model/settings.go
new file mode 100644
index 00000000..a9048316
--- /dev/null
+++ b/manager/model/settings.go
@@ -0,0 +1,7 @@
+package model
+
+type Settings struct {
+	Model
+	Key   string `gorm:"column:key;type:varchar(256);index:uk_settings_key,unique;not null;comment:setting key" json:"key"`
+	Value string `gorm:"column:value;type:varchar(256);not null;comment:setting value" json:"value"`
+}
diff --git a/manager/permission/rbac/rbac.go b/manager/permission/rbac/rbac.go
index a7b47a28..7534a923 100644
--- a/manager/permission/rbac/rbac.go
+++ b/manager/permission/rbac/rbac.go
@@ -18,12 +18,9 @@ package rbac
 
 import (
 	"errors"
-	"fmt"
 	"net/http"
 	"regexp"
-	"strings"
 
-	logger "d7y.io/dragonfly/v2/internal/dflog"
 	managermodel "d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 	"github.com/casbin/casbin/v2"
@@ -56,93 +53,83 @@ func NewEnforcer(gdb *gorm.DB) (*casbin.Enforcer, error) {
 	if err != nil {
 		return nil, err
 	}
+
 	m, err := model.NewModelFromString(modelText)
 	if err != nil {
 		return nil, err
 	}
+
 	enforcer, err := casbin.NewEnforcer(m, adapter)
 	if err != nil {
 		return nil, err
 	}
+
 	return enforcer, nil
 }
 
-func InitRole(e *casbin.Enforcer, g *gin.Engine) error {
-	systemRoles := SystemRoles(g)
-
-	var err error
-	for _, role := range systemRoles {
-		roleInfo := strings.Split(role, ":")
-		_, err = e.AddPolicy(role, roleInfo[0], roleInfo[1])
-		if err != nil {
-			return err
-		}
-
-		// init admin permissions
-		_, err = e.AddPolicy("admin", roleInfo[0], "*")
-		if err != nil {
+func InitRBAC(e *casbin.Enforcer, g *gin.Engine) error {
+	permissions := GetPermissions(g)
+	for _, permission := range permissions {
+		if _, err := e.AddPermissionForUser("root", permission.Object, "*"); err != nil {
 			return err
 		}
 	}
-	if _, err := e.AddRoleForUser("admin", "admin"); err != nil {
+
+	if _, err := e.AddRoleForUser("1", "root"); err != nil {
 		return err
 	}
-	logger.Info("init and check role success")
-	return nil
 
+	return nil
 }
 
-func GetAPIGroupName(path string) (string, error) {
-	apiGroupRegexp := regexp.MustCompile(`^/api/v[0-9]+/([-_a-zA-Z]*)[/.*]*`)
-	matchs := apiGroupRegexp.FindStringSubmatch(path)
-	if len(matchs) != 2 {
-		return "", errors.New("faild to find api group")
-	}
-
-	return matchs[1], nil
+type Permission struct {
+	Object string
+	Action string
 }
 
-func RoleName(object, action string) string {
-	if object == "admin" {
-		return "admin"
+func GetPermissions(g *gin.Engine) []Permission {
+	permissions := []Permission{}
+	actions := []string{"read", "*"}
+	for _, permission := range GetAPIGroupNames(g) {
+		for _, action := range actions {
+			permissions = append(permissions, Permission{
+				Object: permission,
+				Action: action,
+			})
+		}
 	}
-	return fmt.Sprintf("%s:%s", object, action)
+
+	return permissions
 }
 
 func GetAPIGroupNames(g *gin.Engine) []string {
-	APIGroups := []string{}
+	apiGroupNames := []string{}
 	for _, route := range g.Routes() {
-		apiGroupName, err := GetAPIGroupName(route.Path)
+		name, err := GetAPIGroupName(route.Path)
 		if err != nil {
 			continue
 		}
-		if !stringutils.Contains(APIGroups, apiGroupName) {
-			APIGroups = append(APIGroups, apiGroupName)
-		}
 
+		if !stringutils.Contains(apiGroupNames, name) {
+			apiGroupNames = append(apiGroupNames, name)
+		}
 	}
 
-	return APIGroups
+	return apiGroupNames
 }
 
-func SystemRoles(g *gin.Engine) []string {
-	Roles := []string{}
-	policyKeys := []string{"read", "*"}
-
-	for _, apiGroup := range GetAPIGroupNames(g) {
-		for _, p := range policyKeys {
-			if !stringutils.Contains(Roles, apiGroup+":"+p) {
-				Roles = append(Roles, apiGroup+":"+p)
-			}
-
-		}
+func GetAPIGroupName(path string) (string, error) {
+	apiGroupRegexp := regexp.MustCompile(`^/api/v[0-9]+/([-_a-zA-Z]*)[/.*]*`)
+	matchs := apiGroupRegexp.FindStringSubmatch(path)
+	if len(matchs) != 2 {
+		return "", errors.New("cannot find group name")
 	}
-	return Roles
+
+	return matchs[1], nil
 }
 
 func HTTPMethodToAction(method string) string {
 	action := "read"
-
 	if method == http.MethodDelete || method == http.MethodPatch || method == http.MethodPut || method == http.MethodPost {
 		action = "*"
 	}
diff --git a/manager/router/router.go b/manager/router/router.go
index 087bbe16..245bc54d 100644
--- a/manager/router/router.go
+++ b/manager/router/router.go
@@ -79,13 +79,38 @@ func Init(console bool, verbose bool, publicPath string, service service.REST, e
 	apiv1 := r.Group("/api/v1")
 
 	// User
-	ai := apiv1.Group("/users")
-	ai.POST("/signin", jwt.LoginHandler)
-	ai.POST("/signout", jwt.LogoutHandler)
-	ai.POST("/refresh_token", jwt.RefreshHandler)
-	ai.POST("/:id/roles/:role_name", h.AddRoleToUser)
-	ai.DELETE("/:id/roles/:role_name", h.DeleteRoleForUser)
-	ai.POST("/signup", h.SignUp)
+	u := apiv1.Group("/users")
+	u.POST("/signin", jwt.LoginHandler)
+	u.POST("/signout", jwt.LogoutHandler)
+	u.POST("/signup", h.SignUp)
+	u.POST("/refresh_token", jwt.RefreshHandler)
+	u.POST("/:id/reset_password", h.ResetPassword)
+	u.GET("/:id/roles", h.GetRolesForUser)
+	u.PUT("/:id/roles/:role", h.AddRoleToUser)
+	u.DELETE("/:id/roles/:role", h.DeleteRoleForUser)
+
+	// Role
+	re := apiv1.Group("/roles")
+	re.POST("", h.CreateRole)
+	re.DELETE("/:role", h.DestroyRole)
+	re.GET("/:role", h.GetRole)
+	re.GET("", h.GetRoles)
+	re.POST("/:role/permissions", h.AddPermissionForRole)
+	re.DELETE("/:role/permissions", h.DeletePermissionForRole)
+
+	// Permission
+	pm := apiv1.Group("/permissions", jwt.MiddlewareFunc(), rbac)
+	pm.GET("", h.GetPermissions(r))
+
+	// Oauth
+	oa := apiv1.Group("/oauth")
+	oa.GET("", h.GetOauths)
+	oa.GET("/:id", h.GetOauth)
+	oa.DELETE("/:id", h.DestroyOauth)
+	oa.PUT("/:id", h.UpdateOauth)
+	oa.POST("", h.CreateOauth)
+	oa.GET("/signin/:oauth_name", h.OauthSignin)
+	oa.GET("/callback/:oauth_name", h.OauthCallback(jwt))
 
 	// Scheduler Cluster
 	sc := apiv1.Group("/scheduler-clusters")
@@ -104,6 +129,13 @@ func Init(console bool, verbose bool, publicPath string, service service.REST, e
 	s.GET(":id", h.GetScheduler)
 	s.GET("", h.GetSchedulers)
 
+	// Settings
+	st := apiv1.Group("/settings")
+	st.POST("", h.CreateSetting)
+	st.DELETE(":id", h.DestroySetting)
+	st.PATCH("", h.UpdateSetting)
+	st.GET("", h.GetSettings)
+
 	// CDN Cluster
 	cc := apiv1.Group("/cdn-clusters")
 	cc.POST("", h.CreateCDNCluster)
@@ -122,19 +154,6 @@ func Init(console bool, verbose bool, publicPath string, service service.REST, e
 	c.GET(":id", h.GetCDN)
 	c.GET("", h.GetCDNs)
 
-	// Role
-	re := apiv1.Group("/roles", jwt.MiddlewareFunc(), rbac)
-	re.POST("", h.CreateRole)
-	re.GET("", h.GetRoles)
-	re.DELETE("/:role_name", h.DestroyRole)
-	re.GET("/:role_name", h.GetRole)
-	re.POST("/:role_name/permission", h.AddRolePermission)
-	re.DELETE("/:role_name/permission", h.RemoveRolePermission)
-
-	// Permission
-	pn := apiv1.Group("/permissions", jwt.MiddlewareFunc(), rbac)
-	pn.GET("", h.GetPermissions(r))
-
 	// Security Group
 	sg := apiv1.Group("/security-groups")
 	sg.POST("", h.CreateSecurityGroup)
diff --git a/manager/service/oauth.go b/manager/service/oauth.go
new file mode 100644
index 00000000..c8eeb92d
--- /dev/null
+++ b/manager/service/oauth.go
@@ -0,0 +1,122 @@
+package service
+
+import (
+	"d7y.io/dragonfly/v2/manager/auth/oauth"
+	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/manager/types"
+	"golang.org/x/oauth2/github"
+	"golang.org/x/oauth2/google"
+)
+
+func (s *rest) CreateOauth(json types.CreateOauthRequest) (*model.Oauth, error) {
+	o := model.Oauth{}
+	o.ClientID = json.ClientID
+	o.ClientSecret = json.ClientSecret
+	o.Name = json.Name
+	switch json.Name {
+	case oauth.Google:
+		o.AuthURL = google.Endpoint.AuthURL
+		o.TokenURL = google.Endpoint.TokenURL
+		o.Scopes = oauth.GoogleScopes
+		o.UserInfoURL = oauth.GoogleUserInfoURL
+
+	case oauth.Github:
+		o.AuthURL = github.Endpoint.AuthURL
+		o.TokenURL = github.Endpoint.TokenURL
+		o.Scopes = oauth.GithubScopes
+		o.UserInfoURL = oauth.GithubUserInfoURL
+	default:
+		o = model.Oauth{
+			ClientID:     json.ClientID,
+			ClientSecret: json.ClientSecret,
+			Name:         json.Name,
+			Scopes:       json.Scopes,
+			AuthURL:      json.AuthURL,
+			TokenURL:     json.TokenURL,
+			UserInfoURL:  json.UserInfoURL,
+		}
+	}
+
+	if err := s.db.Create(&o).Error; err != nil {
+		return nil, err
+	}
+
+	return &o, nil
+}
+
+func (s *rest) DestroyOauth(id uint) error {
+	if err := s.db.Unscoped().Delete(&model.Oauth{}, id).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *rest) UpdateOauth(id uint, json types.UpdateOauthRequest) (*model.Oauth, error) {
+	oauth := model.Oauth{}
+	if err := s.db.First(&oauth, id).Updates(model.Oauth{
+		ClientID:     json.ClientID,
+		ClientSecret: json.ClientSecret,
+		AuthURL:      json.AuthURL,
+		TokenURL:     json.TokenURL,
+	}).Error; err != nil {
+		return nil, err
+	}
+
+	return &oauth, nil
+}
+
+func (s *rest) GetOauth(id uint) (*model.Oauth, error) {
+	oauth := model.Oauth{}
+	if err := s.db.First(&oauth, id).Error; err != nil {
+		return nil, err
+	}
+
+	return &oauth, nil
+}
+
+func (s *rest) GetOauths() (*[]model.Oauth, error) {
+	oauths := []model.Oauth{}
+	if err := s.db.Find(&oauths).Error; err != nil {
+		return nil, err
+	}
+	return &oauths, nil
+}
+
+func (s *rest) OauthSignin(name string) (string, error) {
+	oauthModel := model.Oauth{}
+	if err := s.db.First(&oauthModel, model.Oauth{Name: name}).Error; err != nil {
+		return "", err
+	}
+
+	o, err := oauth.New(&oauthModel, s.db)
+	if err != nil {
+		return "", err
+	}
+	return o.AuthCodeURL(), nil
+}
+
+func (s *rest) OauthCallback(name, code string) (*model.User, error) {
+	oauthModel := model.Oauth{}
+	if err := s.db.First(&oauthModel, model.Oauth{Name: name}).Error; err != nil {
+		return nil, err
+	}
+	o, err := oauth.New(&oauthModel, s.db)
+	if err != nil {
+		return nil, err
+	}
+
+	token, err := o.ExchangeTokenByCode(code)
+	if err != nil {
+		return nil, err
+	}
+	u, err := o.GetOauthUserInfo(token)
+	if err != nil {
+		return nil, err
+	}
+	user, err := o.OauthLinkUser(u, s.db)
+	if err != nil {
+		return nil, err
+	}
+	return user, nil
+}
diff --git a/manager/service/permission.go b/manager/service/permission.go
index d53c6e5a..04f9e50f 100644
--- a/manager/service/permission.go
+++ b/manager/service/permission.go
@@ -17,100 +17,10 @@
 package service
 
 import (
-	"errors"
-
-	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/permission/rbac"
-	"d7y.io/dragonfly/v2/manager/types"
 	"github.com/gin-gonic/gin"
 )
 
-func (s *rest) GetPermissions(g *gin.Engine) types.Permissions {
-	return rbac.GetAPIGroupNames(g)
-}
-
-func (s *rest) CreateRole(json types.CreateRolePermissionRequest) error {
-	for _, p := range json.Permissions {
-		res, err := s.enforcer.AddPolicy(json.RoleName, p.Object, p.Action)
-		if err != nil {
-			return err
-		}
-		if !res {
-			logger.Infof("The role %s that %s for %s already exist. skip!", json.RoleName, p.Object, p.Action)
-		}
-
-	}
-	return nil
-}
-
-func (s *rest) GetRoles() []string {
-	return s.enforcer.GetAllSubjects()
-}
-
-func (s *rest) AddRolePermission(roleName string, json types.ObjectPermission) error {
-	_, err := s.enforcer.AddPolicy(roleName, json.Object, json.Action)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (s *rest) RemoveRolePermission(roleName string, json types.ObjectPermission) error {
-	_, err := s.enforcer.RemovePolicy(roleName, json.Object, json.Action)
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func (s *rest) DestroyRole(roleName string) error {
-	_, err := s.enforcer.DeleteRole(roleName)
-	s.enforcer.GetAllRoles()
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-func (s *rest) GetRole(roleName string) []map[string]string {
-	result := []map[string]string{}
-	policies := s.enforcer.GetFilteredPolicy(0, roleName)
-	for _, p := range policies {
-		result = append(result, map[string]string{"object": p[1], "action": p[2]})
-	}
-	return result
-}
-
-func (s *rest) GetRolesForUser(UserID uint, currentUserName string) ([]string, error) {
-	var results []string
-	var err error
-	user := model.User{}
-	if err := s.db.First(&user, UserID).Error; err != nil {
-		return nil, err
-	}
-	queryUserName := user.Name
-
-	if queryUserName == currentUserName {
-		results, err = s.enforcer.GetRolesForUser(queryUserName)
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		has, err := s.enforcer.Enforce(currentUserName, "users", "read")
-		if err != nil {
-			return nil, err
-		}
-		if has {
-			results, err = s.enforcer.GetRolesForUser(queryUserName)
-			if err != nil {
-				return nil, err
-			}
-		} else {
-			return nil, errors.New("permission deny")
-		}
-	}
-
-	return results, nil
+func (s *rest) GetPermissions(g *gin.Engine) []rbac.Permission {
+	return rbac.GetPermissions(g)
 }
diff --git a/manager/service/role.go b/manager/service/role.go
new file mode 100644
index 00000000..e6d799ac
--- /dev/null
+++ b/manager/service/role.go
@@ -0,0 +1,52 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package service
+
+import (
+	"d7y.io/dragonfly/v2/manager/types"
+)
+
+func (s *rest) CreateRole(json types.CreateRoleRequest) error {
+	for _, permission := range json.Permissions {
+		_, err := s.enforcer.AddPermissionForUser(json.Role, permission.Object, permission.Action)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (s *rest) DestroyRole(role string) (bool, error) {
+	return s.enforcer.DeleteRole(role)
+}
+
+func (s *rest) GetRole(role string) [][]string {
+	return s.enforcer.GetPermissionsForUser(role)
+}
+
+func (s *rest) GetRoles() []string {
+	return s.enforcer.GetAllRoles()
+}
+
+func (s *rest) AddPermissionForRole(role string, json types.AddPermissionForRoleRequest) (bool, error) {
+	return s.enforcer.AddPermissionForUser(role, json.Object, json.Action)
+}
+
+func (s *rest) DeletePermissionForRole(role string, json types.DeletePermissionForRoleRequest) (bool, error) {
+	return s.enforcer.DeletePermissionForUser(role, json.Object, json.Action)
+}
diff --git a/manager/service/service.go b/manager/service/service.go
index a4d7d863..023f5b65 100644
--- a/manager/service/service.go
+++ b/manager/service/service.go
@@ -21,6 +21,7 @@ import (
 	"d7y.io/dragonfly/v2/manager/database"
 	"d7y.io/dragonfly/v2/manager/job"
 	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/manager/permission/rbac"
 	"d7y.io/dragonfly/v2/manager/types"
 	"github.com/casbin/casbin/v2"
 	"github.com/gin-gonic/gin"
@@ -30,21 +31,21 @@ import (
 )
 
 type REST interface {
-	SignIn(json types.SignInRequest) (*model.User, error)
-	SignUp(json types.SignUpRequest) (*model.User, error)
+	SignIn(types.SignInRequest) (*model.User, error)
+	SignUp(types.SignUpRequest) (*model.User, error)
+	ResetPassword(uint, types.ResetPasswordRequest) error
+	GetRolesForUser(uint) ([]string, error)
+	AddRoleForUser(types.AddRoleForUserParams) (bool, error)
+	DeleteRoleForUser(types.DeleteRoleForUserParams) (bool, error)
 
-	AddRoleForUser(uint, string) error
-	DeleteRoleForUser(uint, string) error
-
-	GetPermissions(*gin.Engine) types.Permissions
-
-	CreateRole(json types.CreateRolePermissionRequest) error
-	DestroyRole(string) error
-	AddRolePermission(string, types.ObjectPermission) error
-	RemoveRolePermission(string, types.ObjectPermission) error
+	CreateRole(json types.CreateRoleRequest) error
+	DestroyRole(string) (bool, error)
+	GetRole(string) [][]string
 	GetRoles() []string
-	GetRole(string) []map[string]string
-	GetRolesForUser(uint, string) ([]string, error)
+	AddPermissionForRole(string, types.AddPermissionForRoleRequest) (bool, error)
+	DeletePermissionForRole(string, types.DeletePermissionForRoleRequest) (bool, error)
+
+	GetPermissions(*gin.Engine) []rbac.Permission
 
 	CreateCDNCluster(types.CreateCDNClusterRequest) (*model.CDNCluster, error)
 	CreateCDNClusterWithSecurityGroupDomain(types.CreateCDNClusterRequest) (*model.CDNCluster, error)
@@ -81,6 +82,12 @@ type REST interface {
 	GetSchedulers(types.GetSchedulersQuery) (*[]model.Scheduler, error)
 	SchedulerTotalCount(types.GetSchedulersQuery) (int64, error)
 
+	CreateSetting(types.CreateSettingRequest) (*model.Settings, error)
+	DestroySetting(string) error
+	UpdateSetting(string, types.UpdateSettingRequest) (*model.Settings, error)
+	GetSettings(types.GetSettingsQuery) (*[]model.Settings, error)
+	SettingTotalCount() (int64, error)
+
 	CreateSecurityGroup(types.CreateSecurityGroupRequest) (*model.SecurityGroup, error)
 	DestroySecurityGroup(uint) error
 	UpdateSecurityGroup(uint, types.UpdateSecurityGroupRequest) (*model.SecurityGroup, error)
@@ -92,6 +99,14 @@ type REST interface {
 
 	CreatePreheat(types.CreatePreheatRequest) (*types.Preheat, error)
 	GetPreheat(string) (*types.Preheat, error)
+
+	CreateOauth(types.CreateOauthRequest) (*model.Oauth, error)
+	DestroyOauth(uint) error
+	UpdateOauth(uint, types.UpdateOauthRequest) (*model.Oauth, error)
+	GetOauth(uint) (*model.Oauth, error)
+	GetOauths() (*[]model.Oauth, error)
+	OauthSignin(name string) (string, error)
+	OauthCallback(name, code string) (*model.User, error)
 }
 
 type rest struct {
diff --git a/manager/service/settings.go b/manager/service/settings.go
new file mode 100644
index 00000000..2ecc28a0
--- /dev/null
+++ b/manager/service/settings.go
@@ -0,0 +1,67 @@
+package service
+
+import (
+	"errors"
+
+	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/manager/types"
+	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+)
+
+var AvaliableSettings = []string{
+	"server_domain",
+}
+
+func (s *rest) CreateSetting(json types.CreateSettingRequest) (*model.Settings, error) {
+	if !stringutils.Contains(AvaliableSettings, json.Key) {
+		return nil, errors.New("invalid setting key")
+	}
+	setting := model.Settings{
+		Key:   json.Key,
+		Value: json.Value,
+	}
+
+	if err := s.db.Create(&setting).Error; err != nil {
+		return nil, err
+	}
+
+	return &setting, nil
+}
+
+func (s *rest) DestroySetting(key string) error {
+	if err := s.db.Unscoped().Delete(&model.Settings{}, model.Settings{Key: key}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *rest) UpdateSetting(key string, json types.UpdateSettingRequest) (*model.Settings, error) {
+	setting := model.Settings{}
+	if err := s.db.First(&setting, model.Settings{Key: key}).Updates(model.Settings{
+		Key:   json.Key,
+		Value: json.Value,
+	}).Error; err != nil {
+		return nil, err
+	}
+
+	return &setting, nil
+}
+
+func (s *rest) GetSettings(q types.GetSettingsQuery) (*[]model.Settings, error) {
+	settings := []model.Settings{}
+	if err := s.db.Scopes(model.Paginate(q.Page, q.PerPage)).Find(&settings).Error; err != nil {
+		return nil, err
+	}
+
+	return &settings, nil
+}
+
+func (s *rest) SettingTotalCount() (int64, error) {
+	var count int64
+	if err := s.db.Model(&model.Settings{}).Count(&count).Error; err != nil {
+		return 0, err
+	}
+
+	return count, nil
+}
diff --git a/manager/service/user.go b/manager/service/user.go
index e0c390bf..910d37b2 100644
--- a/manager/service/user.go
+++ b/manager/service/user.go
@@ -17,6 +17,8 @@
 package service
 
 import (
+	"fmt"
+
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/types"
 	"golang.org/x/crypto/bcrypt"
@@ -38,6 +40,30 @@ func (s *rest) SignIn(json types.SignInRequest) (*model.User, error) {
 	return &user, nil
 }
 
+func (s *rest) ResetPassword(id uint, json types.ResetPasswordRequest) error {
+	user := model.User{}
+	if err := s.db.First(&user, id).Error; err != nil {
+		return err
+	}
+
+	if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(json.OldPassword)); err != nil {
+		return err
+	}
+
+	encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(json.NewPassword), bcrypt.MinCost)
+	if err != nil {
+		return err
+	}
+
+	if err := s.db.First(&user, id).Updates(model.User{
+		EncryptedPassword: string(encryptedPasswordBytes),
+	}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (s *rest) SignUp(json types.SignUpRequest) (*model.User, error) {
 	encryptedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.MinCost)
 	if err != nil {
@@ -62,26 +88,14 @@ func (s *rest) SignUp(json types.SignUpRequest) (*model.User, error) {
 	return &user, nil
 }
 
-func (s *rest) AddRoleForUser(UserID uint, roleName string) error {
-	user := model.User{}
-	if err := s.db.First(&user, UserID).Error; err != nil {
-		return err
-	}
-	_, err := s.enforcer.AddRoleForUser(user.Name, roleName)
-	if err != nil {
-		return err
-	}
-	return nil
+func (s *rest) GetRolesForUser(id uint) ([]string, error) {
+	return s.enforcer.GetRolesForUser(fmt.Sprint(id))
 }
 
-func (s *rest) DeleteRoleForUser(UserID uint, roleName string) error {
-	user := model.User{}
-	if err := s.db.First(&user, UserID).Error; err != nil {
-		return err
-	}
-	_, err := s.enforcer.DeleteRoleForUser(user.Name, roleName)
-	if err != nil {
-		return err
-	}
-	return nil
+func (s *rest) AddRoleForUser(json types.AddRoleForUserParams) (bool, error) {
+	return s.enforcer.AddRoleForUser(fmt.Sprint(json.ID), json.Role)
+}
+
+func (s *rest) DeleteRoleForUser(json types.DeleteRoleForUserParams) (bool, error) {
+	return s.enforcer.DeleteRoleForUser(fmt.Sprint(json.ID), json.Role)
 }
diff --git a/manager/types/oauth.go b/manager/types/oauth.go
new file mode 100644
index 00000000..98835948
--- /dev/null
+++ b/manager/types/oauth.go
@@ -0,0 +1,28 @@
+package types
+
+type OauthParams struct {
+	ID uint `uri:"id" binding:"required"`
+}
+
+type OauthPathParams struct {
+	OauthName string `uri:"oauth_name" binding:"required"`
+}
+
+type OauthBaseRquest struct {
+	Name         string `json:"name" binding:"required"`
+	ClientID     string `json:"client_id" binding:"required"`
+	ClientSecret string `json:"client_secret" binding:"required"`
+	// scope list split by ','
+	Scopes      string `json:"scopes" binding:"omitempty"`
+	AuthURL     string `json:"auth_url" binding:"omitempty"`
+	TokenURL    string `json:"token_url" binding:"omitempty"`
+	UserInfoURL string `json:"user_info_url" binding:"omitempty"`
+}
+
+type CreateOauthRequest struct {
+	OauthBaseRquest
+}
+
+type UpdateOauthRequest struct {
+	OauthBaseRquest
+}
diff --git a/manager/types/permission.go b/manager/types/role.go
similarity index 57%
rename from manager/types/permission.go
rename to manager/types/role.go
index fad65c9f..6d7871b4 100644
--- a/manager/types/permission.go
+++ b/manager/types/role.go
@@ -16,22 +16,23 @@
 
 package types
 
-type RoleParams struct {
-	RoleName string `uri:"role_name" binding:"required,min=1"`
-}
+import (
+	"d7y.io/dragonfly/v2/manager/permission/rbac"
+)
 
-type UserParams struct {
-	ID uint `uri:"user_name" binding:"required"`
+type CreateRoleRequest struct {
+	Role        string            `json:"role" binding:"required"`
+	Permissions []rbac.Permission `json:"permissions" binding:"required"`
 }
 
-type ObjectPermission struct {
-	Object string `json:"object" binding:"required,min=1"`
-	Action string `json:"action" binding:"required,oneof=read *"`
+type RoleParams struct {
+	Role string `uri:"role" binding:"required"`
 }
 
-type CreateRolePermissionRequest struct {
-	RoleName    string             `json:"role_name" binding:"required"`
-	Permissions []ObjectPermission `json:"permissions" binding:"dive"`
+type AddPermissionForRoleRequest struct {
+	rbac.Permission `json:",inline" binding:"required"`
 }
 
-type Permissions []string
+type DeletePermissionForRoleRequest struct {
+	rbac.Permission `json:",inline" binding:"required"`
+}
diff --git a/manager/types/scheduler.go b/manager/types/scheduler.go
index edf6e1d5..f4af6c63 100644
--- a/manager/types/scheduler.go
+++ b/manager/types/scheduler.go
@@ -50,5 +50,5 @@ type GetSchedulersQuery struct {
 	Location           string `form:"location" binding:"omitempty"`
 	IP                 string `form:"ip" binding:"omitempty"`
 	Status             string `form:"status" binding:"omitempty,oneof=active inactive"`
-	SchedulerClusterID uint   `json:"scheduler_cluster_id" binding:"required"`
+	SchedulerClusterID uint   `json:"scheduler_cluster_id" binding:"omitempty"`
 }
diff --git a/manager/types/settings.go b/manager/types/settings.go
new file mode 100644
index 00000000..994dae19
--- /dev/null
+++ b/manager/types/settings.go
@@ -0,0 +1,19 @@
+package types
+
+type SettingParams struct {
+	Key string `uri:"key" binding:"required"`
+}
+type CreateSettingRequest struct {
+	Key   string `json:"key" binding:"required"`
+	Value string `json:"value" binding:"required"`
+}
+
+type UpdateSettingRequest struct {
+	Key   string `json:"key" binding:"required"`
+	Value string `json:"value" binding:"required"`
+}
+
+type GetSettingsQuery struct {
+	Page    int `form:"page" binding:"omitempty,gte=1"`
+	PerPage int `form:"per_page" binding:"omitempty,gte=1,lte=50"`
+}
diff --git a/manager/types/user.go b/manager/types/user.go
index f188123b..319a5857 100644
--- a/manager/types/user.go
+++ b/manager/types/user.go
@@ -16,21 +16,35 @@
 
 package types
 
+type UserParams struct {
+	ID uint `uri:"id" binding:"required"`
+}
+
 type SignInRequest struct {
-	Name     string `form:"name" binding:"required,min=3,max=10"`
-	Password string `form:"password" binding:"required,min=8,max=20"`
+	Name     string `json:"name" binding:"required,min=3,max=10"`
+	Password string `json:"password" binding:"required,min=8,max=20"`
+}
+
+type ResetPasswordRequest struct {
+	OldPassword string `json:"old_password" binding:"required,min=8,max=20"`
+	NewPassword string `json:"new_password" binding:"required,min=8,max=20"`
 }
 
 type SignUpRequest struct {
 	SignInRequest
-	Email    string `form:"email" binding:"required,email"`
-	Phone    string `form:"phone" binding:"omitempty"`
-	Avatar   string `form:"avatar" binding:"omitempty"`
-	Location string `form:"location" binding:"omitempty"`
-	BIO      string `form:"bio" binding:"omitempty"`
+	Email    string `json:"email" binding:"required,email"`
+	Phone    string `json:"phone" binding:"omitempty"`
+	Avatar   string `json:"avatar" binding:"omitempty"`
+	Location string `json:"location" binding:"omitempty"`
+	BIO      string `json:"bio" binding:"omitempty"`
+}
+
+type DeleteRoleForUserParams struct {
+	ID   uint   `uri:"id" binding:"required"`
+	Role string `uri:"role" binding:"required"`
 }
 
-type RoleRequest struct {
-	RoleName string `uri:"role_name" binding:"required,min=1"`
-	ID       uint   `uri:"id" binding:"required,min=1"`
+type AddRoleForUserParams struct {
+	ID   uint   `uri:"id" binding:"required"`
+	Role string `uri:"role" binding:"required"`
 }
diff --git a/pkg/source/httpprotocol/http_source_client.go b/pkg/source/httpprotocol/http_source_client.go
index b74a2460..de4f1e7d 100644
--- a/pkg/source/httpprotocol/http_source_client.go
+++ b/pkg/source/httpprotocol/http_source_client.go
@@ -61,6 +61,7 @@ func init() {
 	}
 
 	transport := http.DefaultTransport.(*http.Transport).Clone()
+	transport.TLSClientConfig.InsecureSkipVerify = true
 	transport.DialContext = (&net.Dialer{
 		Timeout:   3 * time.Second,
 		KeepAlive: 30 * time.Second,
diff --git a/pkg/util/stringutils/string_utils.go b/pkg/util/stringutils/string_utils.go
index c18e1bfa..18152358 100644
--- a/pkg/util/stringutils/string_utils.go
+++ b/pkg/util/stringutils/string_utils.go
@@ -18,7 +18,9 @@
 package stringutils
 
 import (
+	"math/rand"
 	"strings"
+	"time"
 	"unicode"
 )
 
@@ -65,3 +67,13 @@ func Contains(slice []string, ele string) bool {
 
 	return false
 }
+
+func RandString(len int) string {
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	bytes := make([]byte, len)
+	for i := 0; i < len; i++ {
+		b := r.Intn(26) + 65
+		bytes[i] = byte(b)
+	}
+	return string(bytes)
+}


Test patch:
diff --git a/client/daemon/peer/peertask_file_test.go b/client/daemon/peer/peertask_file_test.go
index 83aed0bb..58ae8041 100644
--- a/client/daemon/peer/peertask_file_test.go
+++ b/client/daemon/peer/peertask_file_test.go
@@ -125,7 +125,7 @@ func TestFilePeerTask_BackSource_WithContentLength(t *testing.T) {
 	_, pt, _, err := newFilePeerTask(ctx,
 		ptm.host,
 		ptm.pieceManager,
-		&req.PeerTaskRequest,
+		req,
 		ptm.schedulerClient,
 		ptm.schedulerOption,
 		0)
@@ -243,7 +243,7 @@ func TestFilePeerTask_BackSource_WithoutContentLength(t *testing.T) {
 	_, pt, _, err := newFilePeerTask(ctx,
 		ptm.host,
 		ptm.pieceManager,
-		&req.PeerTaskRequest,
+		req,
 		ptm.schedulerClient,
 		ptm.schedulerOption,
 		0)
diff --git a/manager/permission/rbac/rbac_test.go b/manager/permission/rbac/rbac_test.go
index 8719c32d..9726e486 100644
--- a/manager/permission/rbac/rbac_test.go
+++ b/manager/permission/rbac/rbac_test.go
@@ -57,7 +57,7 @@ func TestGetApiGroupName(t *testing.T) {
 			path: "/api/user",
 			expect: func(t *testing.T, data string, err error) {
 				assert := assert.New(t)
-				assert.EqualError(err, "faild to find api group")
+				assert.EqualError(err, "cannot find group name")
 			},
 		},
 		{
@@ -65,7 +65,7 @@ func TestGetApiGroupName(t *testing.T) {
 			path: "",
 			expect: func(t *testing.T, data string, err error) {
 				assert := assert.New(t)
-				assert.EqualError(err, "faild to find api group")
+				assert.EqualError(err, "cannot find group name")
 			},
 		},
 	}
@@ -78,32 +78,6 @@ func TestGetApiGroupName(t *testing.T) {
 	}
 }
 
-func TestRoleName(t *testing.T) {
-	tests := []struct {
-		object           string
-		action           string
-		exceptedRoleName string
-	}{
-		{
-			object:           "users",
-			action:           "read",
-			exceptedRoleName: "users:read",
-		},
-		{
-			object:           "cdns",
-			action:           "*",
-			exceptedRoleName: "cdns:*",
-		},
-	}
-
-	for _, tt := range tests {
-		roleName := RoleName(tt.object, tt.action)
-		if roleName != tt.exceptedRoleName {
-			t.Errorf("RoleName(%v, %v) = %v, want %v", tt.object, tt.action, roleName, tt.exceptedRoleName)
-		}
-	}
-
-}
 func TestHTTPMethodToAction(t *testing.T) {
 	tests := []struct {
 		method         string
@@ -129,5 +103,4 @@ func TestHTTPMethodToAction(t *testing.T) {
 			t.Errorf("HttpMethodToAction(%v) = %v, want %v", tt.method, action, tt.exceptedAction)
 		}
 	}
-
 }
diff --git a/pkg/util/stringutils/string_utils_test.go b/pkg/util/stringutils/string_utils_test.go
index 7c710248..433c8e91 100644
--- a/pkg/util/stringutils/string_utils_test.go
+++ b/pkg/util/stringutils/string_utils_test.go
@@ -50,3 +50,10 @@ func TestContains(t *testing.T) {
 	assert.True(t, ContainsFold([]string{"a", "B"}, "b"))
 	assert.False(t, Contains([]string{"a", "B"}, "b"))
 }
+
+func TestRandString(t *testing.T) {
+	assert.True(t, len(RandString(10)) == 10)
+	x := RandString(10)
+	y := RandString(10)
+	assert.False(t, x == y)
+}
diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go
index 41429935..33f6b51d 100644
--- a/test/e2e/e2e_test.go
+++ b/test/e2e/e2e_test.go
@@ -30,20 +30,19 @@ var _ = BeforeSuite(func() {
 	out, err := e2eutil.GitCommand("rev-parse", "--short", "HEAD").CombinedOutput()
 	Expect(err).NotTo(HaveOccurred())
 	gitCommit := strings.Fields(string(out))[0]
-	fmt.Println(gitCommit)
+	fmt.Printf("git merge commit: %s\n", gitCommit)
 
 	out, err = e2eutil.KubeCtlCommand("-n", dragonflyNamespace, "get", "pod", "-l", "component=dfdaemon",
 		"-o", "jsonpath='{range .items[*]}{.metadata.name}{end}'").CombinedOutput()
 	podName := strings.Trim(string(out), "'")
 	Expect(err).NotTo(HaveOccurred())
-	fmt.Println(podName)
 
 	Expect(strings.HasPrefix(podName, "dragonfly-dfdaemon-")).Should(BeTrue())
 	pod := e2eutil.NewPodExec(dragonflyNamespace, podName, "dfdaemon")
 	out, err = pod.Command("dfget", "version").CombinedOutput()
 	Expect(err).NotTo(HaveOccurred())
 	dfgetGitCommit := strings.Fields(string(out))[7]
-	fmt.Println(dfgetGitCommit)
+	fmt.Printf("dfget merge commit: %s\n", gitCommit)
 
 	Expect(gitCommit).To(Equal(dfgetGitCommit))
 })