diff --git a/enforcer_interface.go b/enforcer_interface.go index dcc1c01..ae23d75 100644 --- a/enforcer_interface.go +++ b/enforcer_interface.go @@ -70,6 +70,15 @@ type IEnforcer interface { GetRequestToken(ctx ctx.Context) string AddTokenGenerateFun(tokenStyle string, f model.HandlerFunc) error + // QRCode api + CreateQRCodeState(QRCodeId string, timeout int64) error + GetQRCode(QRCodeId string) *model.QRCode + GetQRCodeState(QRCodeId string) model.QRCodeState + GetQRCodeTimeout(QRCodeId string) int64 + Scanned(QRCodeId string, token string) (string, error) + ConfirmAuth(QRCodeTempToken string) error + CancelAuth(QRCodeTempToken string) error + // Access control api SetAuth(manager interface{}) CheckRole(ctx ctx.Context, role string) error diff --git a/enforcer_internal_api.go b/enforcer_internal_api.go index 126efd3..39eef0a 100644 --- a/enforcer_internal_api.go +++ b/enforcer_internal_api.go @@ -1,10 +1,12 @@ package token_go import ( + "fmt" "github.com/weloe/token-go/constant" "github.com/weloe/token-go/ctx" "github.com/weloe/token-go/errors" "github.com/weloe/token-go/model" + "github.com/weloe/token-go/util" "math" "strconv" ) @@ -183,6 +185,35 @@ func (e *Enforcer) deleteByTempToken(service string, tempToken string) error { return e.adapter.DeleteStr(e.spliceTempTokenKey(service, tempToken)) } +func (e *Enforcer) createQRCode(id string, timeout int64) error { + return e.adapter.Set(e.spliceQRCodeKey(id), model.NewQRCode(id), timeout) +} + +func (e *Enforcer) getQRCode(id string) *model.QRCode { + i := e.adapter.Get(e.spliceQRCodeKey(id), util.GetType(&model.QRCode{})) + if i == nil { + return nil + } + return i.(*model.QRCode) +} + +func (e *Enforcer) getQRCodeTimeout(id string) int64 { + return e.adapter.GetTimeout(id) +} + +func (e *Enforcer) updateQRCodeState(id string, state model.QRCodeState) error { + qrCode := e.getQRCode(id) + if qrCode == nil { + return fmt.Errorf("QRCode doesn't exist") + } + qrCode.State = state + return e.updateQRCode(id, qrCode) +} + +func (e *Enforcer) updateQRCode(id string, qrCode *model.QRCode) error { + return e.adapter.Update(e.spliceQRCodeKey(id), qrCode) +} + func (e *Enforcer) getByTempToken(service string, tempToken string) string { return e.adapter.GetStr(e.spliceTempTokenKey(service, tempToken)) } @@ -209,6 +240,10 @@ func (e *Enforcer) spliceTempTokenKey(service string, token string) string { return e.config.TokenName + ":" + "temp-token" + ":temp:" + service + ":" + token } +func (e *Enforcer) spliceQRCodeKey(QRCodeId string) string { + return e.config.TokenName + ":" + "QRCode" + ":QRCode" + QRCodeId +} + func (e *Enforcer) SetJwtSecretKey(key string) { e.config.JwtSecretKey = key } diff --git a/enforcer_manager_api.go b/enforcer_manager_api.go index 0ec5c20..8368d63 100644 --- a/enforcer_manager_api.go +++ b/enforcer_manager_api.go @@ -225,3 +225,99 @@ func (e *Enforcer) ParseTempToken(service string, tempToken string) string { func (e *Enforcer) DeleteTempToken(service string, tempToken string) error { return e.deleteByTempToken(service, tempToken) } + +func (e *Enforcer) CreateQRCodeState(QRCodeId string, timeout int64) error { + return e.createQRCode(QRCodeId, timeout) +} + +// Scanned update state to constant.WaitAuth, return tempToken +func (e *Enforcer) Scanned(QRCodeId string, token string) (string, error) { + err := e.CheckLoginByToken(token) + if err != nil { + return "", err + } + err = e.CheckQRCodeState(QRCodeId, model.WaitScan) + if err != nil { + return "", err + } + err = e.updateQRCodeState(QRCodeId, model.WaitAuth) + if err != nil { + return "", err + } + tempToken, err := e.CreateTempToken(e.config.TokenStyle, "qrCode", QRCodeId, e.config.Timeout) + if err != nil { + return "", err + } + return tempToken, nil +} + +// ConfirmAuth update state to constant.ConfirmAuth +func (e *Enforcer) ConfirmAuth(tempToken string) error { + qrCodeId := e.ParseTempToken("qrCode", tempToken) + if qrCodeId == "" { + return fmt.Errorf("confirm failed, tempToken error: %v", tempToken) + } + err := e.CheckQRCodeState(qrCodeId, model.WaitAuth) + if err != nil { + return err + } + err = e.updateQRCodeState(qrCodeId, model.ConfirmAuth) + if err != nil { + return err + } + err = e.DeleteTempToken("qrCode", tempToken) + if err != nil { + return err + } + return err +} + +// CancelAuth update state to constant.CancelAuth +func (e *Enforcer) CancelAuth(tempToken string) error { + qrCodeId := e.ParseTempToken("qrCode", tempToken) + if qrCodeId == "" { + return fmt.Errorf("confirm failed, tempToken error: %v", qrCodeId) + } + err := e.CheckQRCodeState(qrCodeId, model.WaitAuth) + if err != nil { + return err + } + err = e.updateQRCodeState(qrCodeId, model.CancelAuth) + if err != nil { + return err + } + err = e.DeleteTempToken("qrCode", tempToken) + if err != nil { + return err + } + return err +} + +func (e *Enforcer) GetQRCode(QRCodeId string) *model.QRCode { + return e.getQRCode(QRCodeId) +} + +// GetQRCodeState +// WaitScan = 1 +// WaitAuth = 2 +// ConfirmAuth = 3 +// CancelAuth = 4 +// Expired = 5 +func (e *Enforcer) GetQRCodeState(QRCodeId string) model.QRCodeState { + qrCode := e.getQRCode(QRCodeId) + if qrCode == nil { + return model.Expired + } + return qrCode.State +} + +func (e *Enforcer) GetQRCodeTimeout(QRCodeId string) int64 { + return e.getQRCodeTimeout(QRCodeId) +} + +func (e *Enforcer) CheckQRCodeState(QRCodeId string, want model.QRCodeState) error { + if s := e.GetQRCodeState(QRCodeId); s != want { + return fmt.Errorf("QRCode state error: unexpected state value %v, want is %v", s, want) + } + return nil +} diff --git a/enforcer_manager_api_test.go b/enforcer_manager_api_test.go index 3578d5f..3f35427 100644 --- a/enforcer_manager_api_test.go +++ b/enforcer_manager_api_test.go @@ -1,6 +1,7 @@ package token_go import ( + "github.com/weloe/token-go/model" "testing" ) @@ -33,3 +34,67 @@ func TestEnforcer_TempToken(t *testing.T) { t.Errorf("ParseTempToken() failed, unexpected codeValue: %v", codeValue) } } + +func TestEnforcer_ConfirmQRCode(t *testing.T) { + enforcer, _ := NewTestEnforcer(t) + // in APP + token, err := enforcer.LoginById("1") + if err != nil { + t.Fatalf("Login failed: %v", err) + } + t.Logf("login token: %v", token) + + qrCodeId := "q1" + + err = enforcer.CreateQRCodeState(qrCodeId, -1) + if err != nil { + t.Fatalf("CreateQRCodeState() failed: %v", err) + } + t.Logf("After CreateQRCodeState(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId)) + tempToken, err := enforcer.Scanned(qrCodeId, token) + if err != nil { + t.Fatalf("Scanned() failed: %v", err) + } + t.Logf("After Scanned(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId)) + t.Logf("tempToken: %v", tempToken) + err = enforcer.ConfirmAuth(tempToken) + if err != nil { + t.Fatalf("ConfirmAuth() failed: %v", err) + } + t.Logf("After ConfirmAuth(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId)) + if enforcer.GetQRCodeState(qrCodeId) == model.ConfirmAuth { + t.Logf(" QRCode login successfully.") + } +} + +func TestEnforcer_CancelAuthQRCode(t *testing.T) { + enforcer, _ := NewTestEnforcer(t) + // in APP + token, err := enforcer.LoginById("1") + if err != nil { + t.Fatalf("Login failed: %v", err) + } + t.Logf("login token: %v", token) + + qrCodeId := "q1" + + err = enforcer.CreateQRCodeState(qrCodeId, -1) + if err != nil { + t.Fatalf("CreateQRCodeState() failed: %v", err) + } + t.Logf("After CreateQRCodeState(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId)) + tempToken, err := enforcer.Scanned(qrCodeId, token) + if err != nil { + t.Fatalf("Scanned() failed: %v", err) + } + t.Logf("After Scanned(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId)) + t.Logf("tempToken: %v", tempToken) + err = enforcer.CancelAuth(tempToken) + if err != nil { + t.Fatalf("CancelAuth() failed: %v", err) + } + t.Logf("After CancelAuth(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId)) + if enforcer.GetQRCodeState(qrCodeId) == model.CancelAuth { + t.Logf(" QRCode login is cancelled.") + } +} diff --git a/model/qrcode.go b/model/qrcode.go new file mode 100644 index 0000000..983497c --- /dev/null +++ b/model/qrcode.go @@ -0,0 +1,23 @@ +package model + +type QRCodeState int + +// QRCode State +const ( + WaitScan QRCodeState = 1 + WaitAuth QRCodeState = 2 + ConfirmAuth QRCodeState = 3 + CancelAuth QRCodeState = 4 + Expired QRCodeState = 5 +) + +type QRCode struct { + id string + State QRCodeState + LoginId string + Ticket string +} + +func NewQRCode(id string) *QRCode { + return &QRCode{id: id, State: WaitScan} +}