From aad0592a7e1e9baddff01cc390e99c3f470cd3d4 Mon Sep 17 00:00:00 2001 From: Asaf Shen Date: Fri, 19 Apr 2024 16:35:03 +0300 Subject: [PATCH] Revert "Notp (#429)" This reverts commit b0b6bd26b8fce29238598f2a83688f0e986be2d3. --- README.md | 72 +--- descope/api/client.go | 24 -- descope/errors.go | 1 - descope/internal/auth/auth.go | 31 -- descope/internal/auth/enchantedlink.go | 2 +- descope/internal/auth/notp.go | 66 ---- descope/internal/auth/notp_test.go | 326 ------------------ descope/internal/auth/utils.go | 29 +- descope/sdk/auth.go | 28 -- .../tests/mocks/auth/authenticationmock.go | 53 --- descope/types.go | 6 - 11 files changed, 12 insertions(+), 626 deletions(-) delete mode 100644 descope/internal/auth/notp.go delete mode 100644 descope/internal/auth/notp_test.go diff --git a/README.md b/README.md index 6c6b38ff..b1569d7f 100644 --- a/README.md +++ b/README.md @@ -40,15 +40,14 @@ These sections show how to use the SDK to perform various authentication/authori 2. [Magic Link](#magic-link) 3. [Enchanted Link](#enchanted-link) 4. [OAuth](#oauth) -5. [NOTP (WhatsApp)] (#notp-whatsapp) -6. [SSO (SAML / OIDC)](#sso-saml--oidc) -7. [TOTP Authentication](#totp-authentication) -8. [Passwords](#passwords) -9. [Session Validation](#session-validation) -10. [Roles & Permission Validation](#roles--permission-validation) -11. [Tenant selection](#tenant-selection) -12. [Logging Out](#logging-out) -13. [History](#history) +5. [SSO (SAML / OIDC)](#sso-saml--oidc) +6. [TOTP Authentication](#totp-authentication) +7. [Passwords](#passwords) +8. [Session Validation](#session-validation) +9. [Roles & Permission Validation](#roles--permission-validation) +10. [Tenant selection](#tenant-selection) +11. [Logging Out](#logging-out) +12. [History](#history) ## Management Functions @@ -260,61 +259,6 @@ if err != nil { The session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation) -### NOTP (WhatsApp) - -Using the NOTP (WhatsApp) APIs enables users to log in using their WhatsApp account, according to the following process: -a. The user will be redirected to WhatsApp (with a QR Code or link) with a pre-filled message containing a 16-character alphanumeric code. -b. The user will send the message to the WhatsApp Application associated with the Descope project. -c. Descope will receive the message, validate the code, and send an approval message back to the user. -d. The user will be logged in after receiving the approval message. - -Note: The NOTP (WhatsApp) authentication method should be configured in the Descope Console before using it - -The user can either `sign up`, `sign in`, or `sign up or in`: - -```go -loginID := "" // OR phone number -res, err := descopeClient.Auth.NOTP().SignUpOrIn(context.Background(), loginID, nil, nil) -if err != nil { - // handle error -} - -// The URL to redirect the user to initiate a conversation in the WhatsApp Web Application with the pre-filled message containing the code -res.RedirectURL -// A QR code image that can be displayed to the user to scan using their mobile device camera app to start the WhatsApp conversation -res.Image -// Used to poll for a valid session -res.PendingRef -``` - -After sending the link, you must poll to receive a valid session using the `PendingRef` from the previous step. A valid session will be returned only after the user sends the message to the WhatsApp Application associated with the Project with the code - -```go -// Poll for a certain number of tries / time frame -for i := retriesCount; i > 0; i-- { - authInfo, err := descopeClient.Auth.NOTP().GetSession(context.Background(), res.PendingRef, w) - if err == nil { - // The user successfully authenticated - // The optional `w http.ResponseWriter` adds the session and refresh cookies to the response automatically. - // Otherwise they're available via authInfo - break - } - if errors.Is(err, descope.ErrNOTPUnauthorized) && i > 1 { - // poll again after X seconds - time.Sleep(time.Second * time.Duration(retryInterval)) - continue - } - if err != nil { - // handle error - break - } -} -``` - -The verification process is conducted using the WhatsApp application by the user sending a message with the token included in the link. After sending the message, the user will receive an approval message back, and the session polling will then receive a valid response - -The session and refresh JWTs should be returned to the caller, and passed with every request in the session. Read more on [session validation](#session-validation) - ### SSO (SAML / OIDC) Users can authenticate to a specific tenant using SAML or OIDC. Configure your SSO (SAML / OIDC) settings on the [Descope console](https://app.descope.com/settings/authentication/sso). To start a flow call: diff --git a/descope/api/client.go b/descope/api/client.go index d0b150a8..3545fa1b 100644 --- a/descope/api/client.go +++ b/descope/api/client.go @@ -41,10 +41,6 @@ var ( signUpTOTP: "auth/totp/signup", updateTOTP: "auth/totp/update", verifyTOTPCode: "auth/totp/verify", - signUpNOTP: "auth/notp/whatsapp/signup", - signInNOTP: "auth/notp/whatsapp/signin", - signUpOrInNOTP: "auth/notp/whatsapp/signup-in", - getNOTPSession: "auth/notp/pending-session", verifyCode: "auth/otp/verify", signUpPassword: "auth/password/signup", signInPassword: "auth/password/signin", @@ -226,10 +222,6 @@ type authEndpoints struct { signUpTOTP string updateTOTP string verifyTOTPCode string - signUpNOTP string - signInNOTP string - signUpOrInNOTP string - getNOTPSession string verifyCode string signUpPassword string signInPassword string @@ -419,22 +411,6 @@ func (e *endpoints) UpdateTOTP() string { return path.Join(e.version, e.auth.updateTOTP) } -func (e *endpoints) SignUpNOTP() string { - return path.Join(e.version, e.auth.signUpNOTP) -} - -func (e *endpoints) SignInNOTP() string { - return path.Join(e.version, e.auth.signInNOTP) -} - -func (e *endpoints) SignUpOrInNOTP() string { - return path.Join(e.version, e.auth.signUpOrInNOTP) -} - -func (e *endpoints) GetNOTPSession() string { - return path.Join(e.version, e.auth.getNOTPSession) -} - func (e *endpoints) VerifyCode() string { return path.Join(e.version, e.auth.verifyCode) } diff --git a/descope/errors.go b/descope/errors.go index 8d0105d3..b3c4ac20 100644 --- a/descope/errors.go +++ b/descope/errors.go @@ -19,7 +19,6 @@ var ( ErrEnchantedLinkUnauthorized = newServerError("E062503") ErrPasswordExpired = newServerError("E062909") ErrTokenExpiredByLoggedOut = newServerError("E064001") - ErrNOTPUnauthorized = newServerError("E066103") // server management ErrManagementUserNotFound = newServerError("E112102") diff --git a/descope/internal/auth/auth.go b/descope/internal/auth/auth.go index e69b6a2a..2fa1114f 100644 --- a/descope/internal/auth/auth.go +++ b/descope/internal/auth/auth.go @@ -40,7 +40,6 @@ type authenticationService struct { magicLink sdk.MagicLink enchantedLink sdk.EnchantedLink totp sdk.TOTP - notp sdk.NOTP password sdk.Password webAuthn sdk.WebAuthn oauth sdk.OAuth @@ -60,7 +59,6 @@ func NewAuth(conf AuthParams, c *api.Client) (*authenticationService, error) { authenticationService.sso = &sso{authenticationsBase: base} authenticationService.webAuthn = &webAuthn{authenticationsBase: base} authenticationService.totp = &totp{authenticationsBase: base} - authenticationService.notp = ¬p{authenticationsBase: base} authenticationService.password = &password{authenticationsBase: base} return authenticationService, nil } @@ -81,10 +79,6 @@ func (auth *authenticationService) TOTP() sdk.TOTP { return auth.totp } -func (auth *authenticationService) NOTP() sdk.NOTP { - return auth.notp -} - func (auth *authenticationService) Password() sdk.Password { return auth.password } @@ -836,15 +830,6 @@ func getPendingRefFromResponse(httpResponse *api.HTTPResponse) (*descope.Enchant return response, nil } -func getNOTPResponse(httpResponse *api.HTTPResponse) (*descope.NOTPResponse, error) { - var response *descope.NOTPResponse - if err := utils.Unmarshal([]byte(httpResponse.BodyStr), &response); err != nil { - logger.LogError("Failed to load NOTP response from http response", err) - return response, descope.ErrUnexpectedResponse.WithMessage("Failed to load NOTP response") - } - return response, nil -} - func composeURLMethod(base string, method descope.DeliveryMethod) string { return path.Join(base, string(method)) } @@ -869,22 +854,6 @@ func composeUpdateTOTPURL() string { return api.Routes.UpdateTOTP() } -func composeNOTPSignInURL() string { - return api.Routes.SignInNOTP() -} - -func composeNOTPSignUpURL() string { - return api.Routes.SignUpNOTP() -} - -func composeNOTPSignUpOrInURL() string { - return api.Routes.SignUpOrInNOTP() -} - -func composeNOTPGetSession() string { - return api.Routes.GetNOTPSession() -} - func composeVerifyCodeURL(method descope.DeliveryMethod) string { return composeURLMethod(api.Routes.VerifyCode(), method) } diff --git a/descope/internal/auth/enchantedlink.go b/descope/internal/auth/enchantedlink.go index 3f48172c..c8ca7ed7 100644 --- a/descope/internal/auth/enchantedlink.go +++ b/descope/internal/auth/enchantedlink.go @@ -68,7 +68,7 @@ func (auth *enchantedLink) SignUpOrIn(ctx context.Context, loginID, URI string, func (auth *enchantedLink) GetSession(ctx context.Context, pendingRef string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { var err error - httpResponse, err := auth.client.DoPostRequest(ctx, composeGetSession(), newAuthenticationGetSessionBody(pendingRef), nil, "") + httpResponse, err := auth.client.DoPostRequest(ctx, composeGetSession(), newAuthenticationGetMagicLinkSessionBody(pendingRef), nil, "") if err != nil { return nil, err } diff --git a/descope/internal/auth/notp.go b/descope/internal/auth/notp.go deleted file mode 100644 index 11717e66..00000000 --- a/descope/internal/auth/notp.go +++ /dev/null @@ -1,66 +0,0 @@ -package auth - -import ( - "context" - "net/http" - - "github.com/descope/go-sdk/descope" -) - -type notp struct { - authenticationsBase -} - -func (auth *notp) SignIn(ctx context.Context, loginID string, r *http.Request, loginOptions *descope.LoginOptions) (*descope.NOTPResponse, error) { - var pswd string - var err error - if loginOptions.IsJWTRequired() { - pswd, err = getValidRefreshToken(r) - if err != nil { - return nil, descope.ErrInvalidStepUpJWT - } - } - httpResponse, err := auth.client.DoPostRequest(ctx, composeNOTPSignInURL(), newNOTPAuthenticationRequestBody(loginID, loginOptions), nil, pswd) - if err != nil { - return nil, err - } - return getNOTPResponse(httpResponse) -} - -func (auth *notp) SignUp(ctx context.Context, loginID string, user *descope.User, signUpOptions *descope.SignUpOptions) (*descope.NOTPResponse, error) { - if user == nil { - user = &descope.User{} - } - if len(user.Phone) == 0 { - user.Phone = loginID - } - - httpResponse, err := auth.client.DoPostRequest(ctx, composeNOTPSignUpURL(), newNOTPAuthenticationSignUpRequestBody(loginID, user, signUpOptions), nil, "") - if err != nil { - return nil, err - } - return getNOTPResponse(httpResponse) -} - -func (auth *notp) SignUpOrIn(ctx context.Context, loginID string, signUpOptions *descope.SignUpOptions) (*descope.NOTPResponse, error) { - if signUpOptions == nil { - signUpOptions = &descope.SignUpOptions{} - } - httpResponse, err := auth.client.DoPostRequest(ctx, composeNOTPSignUpOrInURL(), newNOTPAuthenticationRequestBody(loginID, &descope.LoginOptions{ - CustomClaims: signUpOptions.CustomClaims, - TemplateOptions: signUpOptions.TemplateOptions, - }), nil, "") - if err != nil { - return nil, err - } - return getNOTPResponse(httpResponse) -} - -func (auth *notp) GetSession(ctx context.Context, pendingRef string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { - var err error - httpResponse, err := auth.client.DoPostRequest(ctx, composeNOTPGetSession(), newAuthenticationGetSessionBody(pendingRef), nil, "") - if err != nil { - return nil, err - } - return auth.generateAuthenticationInfo(httpResponse, w) -} diff --git a/descope/internal/auth/notp_test.go b/descope/internal/auth/notp_test.go deleted file mode 100644 index 805dc3ed..00000000 --- a/descope/internal/auth/notp_test.go +++ /dev/null @@ -1,326 +0,0 @@ -package auth - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/descope/go-sdk/descope" - "github.com/descope/go-sdk/descope/api" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - image = "image-1" - redirectURL = "url-1" -) - -func TestSignInNOTPEmptyLoginID(t *testing.T) { - phone := "" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"pendingRef": "pr1","image": "image1", "redirectUrl":"redirect-1"}`)), - }, nil - }) - require.NoError(t, err) - _, err = a.NOTP().SignIn(context.Background(), phone, nil, nil) - require.NoError(t, err) -} - -func TestSignInNOTPStepupNoJwt(t *testing.T) { - phone := "+111111111111" - a, err := newTestAuth(nil, nil) - require.NoError(t, err) - _, err = a.NOTP().SignIn(context.Background(), phone, nil, &descope.LoginOptions{Stepup: true}) - require.Error(t, err) - assert.ErrorIs(t, err, descope.ErrInvalidStepUpJWT) -} - -func TestSignInNOTP(t *testing.T) { - phone := "+111111111111" - pendingRefResponse := "pending_ref" - - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - assert.EqualValues(t, composeNOTPSignInURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["loginId"]) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"pendingRef": "%s","image": "%s", "redirectUrl":"%s"}`, pendingRefResponse, image, redirectURL))), - }, nil - }) - require.NoError(t, err) - response, err := a.NOTP().SignIn(context.Background(), phone, nil, nil) - require.NoError(t, err) - require.EqualValues(t, pendingRefResponse, response.PendingRef) - require.EqualValues(t, image, response.Image) - require.EqualValues(t, redirectURL, response.RedirectURL) -} - -func TestSignInNOTPStepup(t *testing.T) { - phone := "+111111111111" - pendingRefResponse := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - assert.EqualValues(t, composeNOTPSignInURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["loginId"]) - assert.EqualValues(t, map[string]interface{}{"stepup": true, "customClaims": map[string]interface{}{"k1": "v1"}}, m["loginOptions"]) - reqToken := r.Header.Get(api.AuthorizationHeaderName) - splitToken := strings.Split(reqToken, api.BearerAuthorizationPrefix) - require.Len(t, splitToken, 2) - bearer := splitToken[1] - bearers := strings.Split(bearer, ":") - require.Len(t, bearers, 2) - assert.EqualValues(t, "test", bearers[1]) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"pendingRef": "%s","image": "%s"}`, pendingRefResponse, image))), - }, nil - }) - require.NoError(t, err) - response, err := a.NOTP().SignIn(context.Background(), phone, &http.Request{Header: http.Header{"Cookie": []string{"DSR=test"}}}, &descope.LoginOptions{Stepup: true, CustomClaims: map[string]interface{}{"k1": "v1"}}) - require.NoError(t, err) - require.EqualValues(t, pendingRefResponse, response.PendingRef) - require.EqualValues(t, image, response.Image) -} - -func TestSignInNOTPInvalidResponse(t *testing.T) { - phone := "+111111111111" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"pendingRef"`)), - }, nil - }) - require.NoError(t, err) - res, err := a.NOTP().SignIn(context.Background(), phone, nil, nil) - require.Error(t, err) - require.Empty(t, res) -} - -func TestSignUpNOTP(t *testing.T) { - phone := "+111111111111" - pendingRefResponse := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - assert.EqualValues(t, composeNOTPSignUpURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["phone"]) - assert.EqualValues(t, phone, m["loginId"]) - assert.EqualValues(t, "test", m["user"].(map[string]interface{})["name"]) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"pendingRef": "%s","image": "%s"}`, pendingRefResponse, image))), - }, nil - }) - require.NoError(t, err) - response, err := a.NOTP().SignUp(context.Background(), phone, &descope.User{Name: "test"}, nil) - require.NoError(t, err) - require.EqualValues(t, pendingRefResponse, response.PendingRef) - require.EqualValues(t, image, response.Image) -} - -func TestSignUpNOTPWithSignUpOptions(t *testing.T) { - phone := "+111111111111" - pendingRefResponse := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - assert.EqualValues(t, composeNOTPSignUpURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["phone"]) - assert.EqualValues(t, phone, m["loginId"]) - assert.EqualValues(t, "test", m["user"].(map[string]interface{})["name"]) - assert.EqualValues(t, map[string]interface{}{"customClaims": map[string]interface{}{"aa": "bb"}, "templateOptions": map[string]interface{}{"cc": "dd"}}, m["loginOptions"]) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"pendingRef": "%s","image": "%s"}`, pendingRefResponse, image))), - }, nil - }) - require.NoError(t, err) - response, err := a.NOTP().SignUp(context.Background(), phone, &descope.User{Name: "test"}, &descope.SignUpOptions{ - CustomClaims: map[string]interface{}{"aa": "bb"}, - TemplateOptions: map[string]string{"cc": "dd"}, - }) - require.NoError(t, err) - require.EqualValues(t, pendingRefResponse, response.PendingRef) - require.EqualValues(t, image, response.Image) -} - -func TestSignUpOrInNOTP(t *testing.T) { - phone := "+111111111111" - pendingRefResponse := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - assert.EqualValues(t, composeNOTPSignUpOrInURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["loginId"]) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"pendingRef": "%s", "image": "%s"}`, pendingRefResponse, image))), - }, nil - }) - require.NoError(t, err) - response, err := a.NOTP().SignUpOrIn(context.Background(), phone, nil) - require.NoError(t, err) - require.EqualValues(t, pendingRefResponse, response.PendingRef) - require.EqualValues(t, image, response.Image) -} - -func TestSignUpOrInNOTPWithLoginOptions(t *testing.T) { - phone := "+111111111111" - pendingRefResponse := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - assert.EqualValues(t, composeNOTPSignUpOrInURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["loginId"]) - assert.EqualValues(t, map[string]interface{}{"customClaims": map[string]interface{}{"aa": "bb"}, "templateOptions": map[string]interface{}{"cc": "dd"}}, m["loginOptions"]) - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"pendingRef": "%s", "image": "%s"}`, pendingRefResponse, image))), - }, nil - }) - require.NoError(t, err) - response, err := a.NOTP().SignUpOrIn(context.Background(), phone, &descope.SignUpOptions{ - CustomClaims: map[string]interface{}{"aa": "bb"}, - TemplateOptions: map[string]string{"cc": "dd"}, - }) - require.NoError(t, err) - require.EqualValues(t, pendingRefResponse, response.PendingRef) - require.EqualValues(t, image, response.Image) -} - -func TestSignUpNOTPEmptyLoginID(t *testing.T) { - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"pendingRef": "pr1","image": "image1", "redirectUrl":"redirect-1"}`)), - }, nil - }) - require.NoError(t, err) - _, err = a.NOTP().SignUp(context.Background(), "", &descope.User{Name: "test"}, nil) - require.NoError(t, err) -} - -func TestNOTPGetSession(t *testing.T) { - pendingRef := "pending_ref" - a, err := newTestAuth(nil, DoOk(func(r *http.Request) { - assert.EqualValues(t, composeNOTPGetSession(), r.URL.RequestURI()) - - body, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, pendingRef, body["pendingRef"]) - })) - require.NoError(t, err) - w := httptest.NewRecorder() - info, err := a.NOTP().GetSession(context.Background(), pendingRef, w) - require.NoError(t, err) - assert.NotEmpty(t, info.SessionToken.JWT) - require.Len(t, w.Result().Cookies(), 1) // Just the refresh token -} - -func TestNOTPGetSessionGenerateAuthenticationInfoValidDSRCookie(t *testing.T) { - pendingRef := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - cookie := &http.Cookie{Name: descope.RefreshCookieName, Value: jwtRTokenValid} // valid token - return &http.Response{StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(mockAuthSessionBodyNoRefreshJwt)), - Header: http.Header{"Set-Cookie": []string{cookie.String()}}, - }, nil - }) - a.conf.SessionJWTViaCookie = true - require.NoError(t, err) - - w := httptest.NewRecorder() - info, err := a.NOTP().GetSession(context.Background(), pendingRef, w) - require.NoError(t, err) - assert.NotEmpty(t, info.SessionToken.JWT) - assert.NotEmpty(t, info.RefreshToken.JWT) // make sure refresh token exist -} - -func TestNOTPGetSessionGenerateAuthenticationInfoInValidDSRCookie(t *testing.T) { - pendingRef := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - cookie := &http.Cookie{Name: descope.RefreshCookieName, Value: jwtTokenExpired} // invalid token - return &http.Response{StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(mockAuthSessionBodyNoRefreshJwt)), - Header: http.Header{"Set-Cookie": []string{cookie.String()}}, - }, nil - }) - a.conf.SessionJWTViaCookie = true - require.NoError(t, err) - - w := httptest.NewRecorder() - _, err = a.NOTP().GetSession(context.Background(), pendingRef, w) - require.Error(t, err) // should get error as Refresh cookie is invalid -} - -func TesNOTPtGetSessionGenerateAuthenticationInfoNoDSRCookie(t *testing.T) { - pendingRef := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - return &http.Response{StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(mockAuthSessionBodyNoRefreshJwt)), - }, nil - }) - a.conf.SessionJWTViaCookie = true - require.NoError(t, err) - - w := httptest.NewRecorder() - info, err := a.NOTP().GetSession(context.Background(), pendingRef, w) - require.NoError(t, err) - assert.NotEmpty(t, info.SessionToken.JWT) - assert.Nil(t, info.RefreshToken) // there is no DSR cookie so refresh token is not exist (not on body and not on cookie) -} - -func TestGetNOTPSessionError(t *testing.T) { - pendingRef := "pending_ref" - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - return &http.Response{StatusCode: http.StatusBadGateway}, nil - }) - require.NoError(t, err) - w := httptest.NewRecorder() - _, err = a.NOTP().GetSession(context.Background(), pendingRef, w) - require.Error(t, err) -} - -func TestSignUpNOTPNoUser(t *testing.T) { - phone := "+111111111111" - a, err := newTestAuth(nil, DoOk(func(r *http.Request) { - assert.EqualValues(t, composeNOTPSignUpURL(), r.URL.RequestURI()) - - m, err := readBodyMap(r) - require.NoError(t, err) - assert.EqualValues(t, phone, m["phone"]) - assert.EqualValues(t, phone, m["loginId"]) - assert.EqualValues(t, phone, m["user"].(map[string]interface{})["phone"]) - })) - require.NoError(t, err) - _, err = a.NOTP().SignUp(context.Background(), phone, nil, nil) - require.NoError(t, err) -} -func TestSignUpOrInNOTPNoLoginID(t *testing.T) { - a, err := newTestAuth(nil, func(r *http.Request) (*http.Response, error) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"pendingRef": "pr1","image": "image1", "redirectUrl":"redirect-1"}`)), - }, nil - }) - require.NoError(t, err) - _, err = a.NOTP().SignUpOrIn(context.Background(), "", nil) - require.NoError(t, err) -} diff --git a/descope/internal/auth/utils.go b/descope/internal/auth/utils.go index a945fed7..7a71e9df 100644 --- a/descope/internal/auth/utils.go +++ b/descope/internal/auth/utils.go @@ -80,11 +80,6 @@ type totpSignUpRequestBody struct { User *descope.User `json:"user,omitempty"` } -type notpAuthenticationRequestBody struct { - *authenticationRequestBody `json:",inline"` - LoginOptions *descope.LoginOptions `json:"loginOptions,omitempty"` -} - type otpUpdateEmailRequestBody struct { *descope.UpdateOptions `json:",inline"` LoginID string `json:"loginId,omitempty"` @@ -130,7 +125,7 @@ type magicLinkAuthenticationVerifyRequestBody struct { Token string `json:"token"` } -type authenticationGetSessionBody struct { +type authenticationGetMagicLinkSessionBody struct { PendingRef string `json:"pendingRef"` } @@ -163,24 +158,6 @@ func newOTPUpdateEmailRequestBody(loginID, email string, updateOptions *descope. return &otpUpdateEmailRequestBody{LoginID: loginID, Email: email, UpdateOptions: updateOptions} } -func newNOTPAuthenticationRequestBody(loginID string, loginOptions *descope.LoginOptions) *notpAuthenticationRequestBody { - return ¬pAuthenticationRequestBody{authenticationRequestBody: newSignInRequestBody(loginID, loginOptions), LoginOptions: loginOptions} -} - -func newNOTPAuthenticationSignUpRequestBody(loginID string, user *descope.User, signUpOptions *descope.SignUpOptions) *authenticationSignUpRequestBody { - res := newSignUpRequestBody(descope.MethodSMS, user) - res.User = user - res.LoginID = loginID - if signUpOptions == nil { - signUpOptions = &descope.SignUpOptions{} - } - res.LoginOptions = &descope.LoginOptions{ - CustomClaims: signUpOptions.CustomClaims, - TemplateOptions: signUpOptions.TemplateOptions, - } - return res -} - func newOTPUpdatePhoneRequestBody(loginID, phone string, updateOptions *descope.UpdateOptions) *otpUpdatePhoneRequestBody { return &otpUpdatePhoneRequestBody{LoginID: loginID, Phone: phone, UpdateOptions: updateOptions} } @@ -237,8 +214,8 @@ func newMagicLinkUpdatePhoneRequestBody(loginID, phone string, URI string, cross return &magicLinkUpdatePhoneRequestBody{LoginID: loginID, Phone: phone, URI: URI, CrossDevice: crossDevice, UpdateOptions: updateOptions} } -func newAuthenticationGetSessionBody(pendingRef string) *authenticationGetSessionBody { - return &authenticationGetSessionBody{PendingRef: pendingRef} +func newAuthenticationGetMagicLinkSessionBody(pendingRef string) *authenticationGetMagicLinkSessionBody { + return &authenticationGetMagicLinkSessionBody{PendingRef: pendingRef} } func newExchangeTokenBody(code string) *exchangeTokenBody { diff --git a/descope/sdk/auth.go b/descope/sdk/auth.go index 88c10f67..93853759 100644 --- a/descope/sdk/auth.go +++ b/descope/sdk/auth.go @@ -134,33 +134,6 @@ type TOTP interface { UpdateUser(ctx context.Context, loginID string, request *http.Request) (*descope.TOTPResponse, error) } -type NOTP interface { - // SignIn - Use to login a user with NOTP (WhatsApp) authentication. - // The Login ID should be a phone or empty. If empty, the whatsapp phone number will be used as the login ID in the verification process. - // Use the redirect URL / Image (QR Code) in the response to take the user to the WhatsApp app to scan the QR code. - // The jwt would be returned on the GetSession function after the user has sent the verification message. - // Returns an error upon failure. - SignIn(ctx context.Context, loginID string, r *http.Request, loginOptions *descope.LoginOptions) (*descope.NOTPResponse, error) - - // SignUp - Use to create a new user with NOTP (WhatsApp) authentication. - // The Login ID should be a phone or empty. If empty, the whatsapp phone number will be used as the login ID in the verification process. - // Use the redirect URL / Image (QR Code) in the response to take the user to the WhatsApp app to scan the QR code. - // The jwt would be returned on the GetSession function after the user has sent the verification message. - // Returns an error upon failure. - SignUp(ctx context.Context, loginID string, user *descope.User, signUpOptions *descope.SignUpOptions) (*descope.NOTPResponse, error) - - // SignUpOrIn - Use to login in with NOTP (WhatsApp), if user does not exist, a new user will be created - // The Login ID should be a phone or empty. If empty, the whatsapp phone number will be used as the login ID in the verification process. - // Use the redirect URL / Image (QR Code) in the response to take the user to the WhatsApp app to scan the QR code. - // The jwt would be returned on the GetSession function after the user has sent the verification message. - // Returns an error upon failure. - SignUpOrIn(ctx context.Context, loginID string, signUpOptions *descope.SignUpOptions) (*descope.NOTPResponse, error) - - // GetSession - Use to get a session that was generated by SignIn/SignUp/SignUpOrIn request. - // This function will return a proper JWT only after Verify succeed for this sign-in/sign-up/sign-up-or-in. - GetSession(ctx context.Context, pendingRef string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) -} - type Password interface { // SignUp - Use to create a new user that authenticates with a password. // Use the ResponseWriter (optional) to apply the cookies to the response automatically. @@ -317,7 +290,6 @@ type Authentication interface { EnchantedLink() EnchantedLink OTP() OTP TOTP() TOTP - NOTP() NOTP Password() Password OAuth() OAuth SAML() SAML diff --git a/descope/tests/mocks/auth/authenticationmock.go b/descope/tests/mocks/auth/authenticationmock.go index 63b41318..ec6be8b4 100644 --- a/descope/tests/mocks/auth/authenticationmock.go +++ b/descope/tests/mocks/auth/authenticationmock.go @@ -13,7 +13,6 @@ type MockAuthentication struct { *MockEnchantedLink *MockOTP *MockTOTP - *MockNOTP *MockPassword *MockOAuth *MockSAML @@ -38,10 +37,6 @@ func (m *MockAuthentication) TOTP() sdk.TOTP { return m.MockTOTP } -func (m *MockAuthentication) NOTP() sdk.NOTP { - return m.MockNOTP -} - func (m *MockAuthentication) Password() sdk.Password { return m.MockPassword } @@ -298,54 +293,6 @@ func (m *MockTOTP) UpdateUser(_ context.Context, loginID string, r *http.Request return m.UpdateUserResponse, m.UpdateUserError } -// Mock NOTP - -type MockNOTP struct { - SignInAssert func(loginID string, r *http.Request, loginOptions *descope.LoginOptions) - SignInError error - SignInResponse *descope.NOTPResponse - - SignUpAssert func(loginID string, user *descope.User, signUpOptions *descope.SignUpOptions) - SignUpError error - SignUpResponse *descope.NOTPResponse - - SignUpOrInAssert func(loginID string, signUpOptions *descope.SignUpOptions) - SignUpOrInError error - SignUpOrInResponse *descope.NOTPResponse - - GetSessionAssert func(pendingRef string, w http.ResponseWriter) - GetSessionResponse *descope.AuthenticationInfo - GetSessionError error -} - -func (m *MockNOTP) SignIn(_ context.Context, loginID string, r *http.Request, loginOptions *descope.LoginOptions) (*descope.NOTPResponse, error) { - if m.SignInAssert != nil { - m.SignInAssert(loginID, r, loginOptions) - } - return m.SignInResponse, m.SignInError -} - -func (m *MockNOTP) SignUp(_ context.Context, loginID string, user *descope.User, signUpOptions *descope.SignUpOptions) (*descope.NOTPResponse, error) { - if m.SignUpAssert != nil { - m.SignUpAssert(loginID, user, signUpOptions) - } - return m.SignUpResponse, m.SignUpError -} - -func (m *MockNOTP) SignUpOrIn(_ context.Context, loginID string, signUpOptions *descope.SignUpOptions) (*descope.NOTPResponse, error) { - if m.SignUpOrInAssert != nil { - m.SignUpOrInAssert(loginID, signUpOptions) - } - return m.SignUpOrInResponse, m.SignUpOrInError -} - -func (m *MockNOTP) GetSession(_ context.Context, pendingRef string, w http.ResponseWriter) (*descope.AuthenticationInfo, error) { - if m.GetSessionAssert != nil { - m.GetSessionAssert(pendingRef, w) - } - return m.GetSessionResponse, m.GetSessionError -} - // Mock Password type MockPassword struct { diff --git a/descope/types.go b/descope/types.go index ed3141cc..9f1bd26d 100644 --- a/descope/types.go +++ b/descope/types.go @@ -17,12 +17,6 @@ type TOTPResponse struct { Key string `json:"key,omitempty"` } -type NOTPResponse struct { - RedirectURL string `json:"redirectUrl,omitempty"` - Image string `json:"image,omitempty"` - PendingRef string `json:"pendingRef,omitempty"` // Pending referral code used to poll the authentication info -} - type AuthenticationInfo struct { SessionToken *Token `json:"token,omitempty"` RefreshToken *Token `json:"refreshToken,omitempty"`