diff --git a/stytch/b2b/magiclinks.go b/stytch/b2b/magiclinks.go index 511e806..bf2a126 100644 --- a/stytch/b2b/magiclinks.go +++ b/stytch/b2b/magiclinks.go @@ -9,7 +9,9 @@ package b2b import ( "context" "encoding/json" + "fmt" + "github.com/mitchellh/mapstructure" "github.com/stytchauth/stytch-go/v11/stytch" "github.com/stytchauth/stytch-go/v11/stytch/b2b/magiclinks" "github.com/stytchauth/stytch-go/v11/stytch/stytcherror" @@ -72,3 +74,64 @@ func (c *MagicLinksClient) Authenticate( ) return &retVal, err } + +// AuthenticateWithClaims fills in the claims pointer with custom claims from the response. +// Pass in a map with the types of values you're expecting so that this function can marshal +// the claims from the response. See ExampleClient_AuthenticateWithClaims_map, +// ExampleClient_AuthenticateWithClaims_struct for examples +func (c *MagicLinksClient) AuthenticateWithClaims( + ctx context.Context, + body *magiclinks.AuthenticateParams, + claims any, +) (*magiclinks.AuthenticateResponse, error) { + var jsonBody []byte + var err error + if body != nil { + jsonBody, err = json.Marshal(body) + if err != nil { + return nil, stytcherror.NewClientLibraryError("error marshaling request body") + } + } + + b, err := c.C.RawRequest( + ctx, + "POST", + "/v1/b2b/magic_links/authenticate", + nil, + jsonBody, + ) + if err != nil { + return nil, err + } + + // First extract the Stytch data. + var retVal magiclinks.AuthenticateResponse + if err := json.Unmarshal(b, &retVal); err != nil { + return nil, fmt.Errorf("unmarshal magiclinks.AuthenticateResponse: %w", err) + } + + if claims == nil { + return &retVal, nil + } + + if m, ok := claims.(*map[string]any); ok { + *m = retVal.MemberSession.CustomClaims + return &retVal, nil + } + + // This is where we need to convert claims into a claimsMap + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &claims, + TagName: "json", + }) + if err != nil { + return nil, err + } + + err = decoder.Decode(retVal.MemberSession.CustomClaims) + if err != nil { + return nil, err + } + + return &retVal, err +} diff --git a/stytch/b2b/oauth.go b/stytch/b2b/oauth.go index fa04793..2846fa6 100644 --- a/stytch/b2b/oauth.go +++ b/stytch/b2b/oauth.go @@ -9,7 +9,9 @@ package b2b import ( "context" "encoding/json" + "fmt" + "github.com/mitchellh/mapstructure" "github.com/stytchauth/stytch-go/v11/stytch" "github.com/stytchauth/stytch-go/v11/stytch/b2b/oauth" "github.com/stytchauth/stytch-go/v11/stytch/stytcherror" @@ -69,3 +71,64 @@ func (c *OAuthClient) Authenticate( ) return &retVal, err } + +// AuthenticateWithClaims fills in the claims pointer with custom claims from the response. +// Pass in a map with the types of values you're expecting so that this function can marshal +// the claims from the response. See ExampleClient_AuthenticateWithClaims_map, +// ExampleClient_AuthenticateWithClaims_struct for examples +func (c *OAuthClient) AuthenticateWithClaims( + ctx context.Context, + body *oauth.AuthenticateParams, + claims any, +) (*oauth.AuthenticateResponse, error) { + var jsonBody []byte + var err error + if body != nil { + jsonBody, err = json.Marshal(body) + if err != nil { + return nil, stytcherror.NewClientLibraryError("error marshaling request body") + } + } + + b, err := c.C.RawRequest( + ctx, + "POST", + "/v1/b2b/oauth/authenticate", + nil, + jsonBody, + ) + if err != nil { + return nil, err + } + + // First extract the Stytch data. + var retVal oauth.AuthenticateResponse + if err := json.Unmarshal(b, &retVal); err != nil { + return nil, fmt.Errorf("unmarshal oauth.AuthenticateResponse: %w", err) + } + + if claims == nil { + return &retVal, nil + } + + if m, ok := claims.(*map[string]any); ok { + *m = retVal.MemberSession.CustomClaims + return &retVal, nil + } + + // This is where we need to convert claims into a claimsMap + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &claims, + TagName: "json", + }) + if err != nil { + return nil, err + } + + err = decoder.Decode(retVal.MemberSession.CustomClaims) + if err != nil { + return nil, err + } + + return &retVal, err +} diff --git a/stytch/b2b/otp_sms.go b/stytch/b2b/otp_sms.go index dd3c3d6..cb1f914 100644 --- a/stytch/b2b/otp_sms.go +++ b/stytch/b2b/otp_sms.go @@ -9,7 +9,9 @@ package b2b import ( "context" "encoding/json" + "fmt" + "github.com/mitchellh/mapstructure" "github.com/stytchauth/stytch-go/v11/stytch" "github.com/stytchauth/stytch-go/v11/stytch/b2b/otp/sms" "github.com/stytchauth/stytch-go/v11/stytch/stytcherror" @@ -124,3 +126,64 @@ func (c *OTPsSmsClient) Authenticate( ) return &retVal, err } + +// AuthenticateWithClaims fills in the claims pointer with custom claims from the response. +// Pass in a map with the types of values you're expecting so that this function can marshal +// the claims from the response. See ExampleClient_AuthenticateWithClaims_map, +// ExampleClient_AuthenticateWithClaims_struct for examples +func (c *OTPsSmsClient) AuthenticateWithClaims( + ctx context.Context, + body *sms.AuthenticateParams, + claims any, +) (*sms.AuthenticateResponse, error) { + var jsonBody []byte + var err error + if body != nil { + jsonBody, err = json.Marshal(body) + if err != nil { + return nil, stytcherror.NewClientLibraryError("error marshaling request body") + } + } + + b, err := c.C.RawRequest( + ctx, + "POST", + "/v1/b2b/otps/sms/authenticate", + nil, + jsonBody, + ) + if err != nil { + return nil, err + } + + // First extract the Stytch data. + var retVal sms.AuthenticateResponse + if err := json.Unmarshal(b, &retVal); err != nil { + return nil, fmt.Errorf("unmarshal sms.AuthenticateResponse: %w", err) + } + + if claims == nil { + return &retVal, nil + } + + if m, ok := claims.(*map[string]any); ok { + *m = retVal.MemberSession.CustomClaims + return &retVal, nil + } + + // This is where we need to convert claims into a claimsMap + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &claims, + TagName: "json", + }) + if err != nil { + return nil, err + } + + err = decoder.Decode(retVal.MemberSession.CustomClaims) + if err != nil { + return nil, err + } + + return &retVal, err +} diff --git a/stytch/b2b/passwords.go b/stytch/b2b/passwords.go index d312d16..4c1221d 100644 --- a/stytch/b2b/passwords.go +++ b/stytch/b2b/passwords.go @@ -9,7 +9,9 @@ package b2b import ( "context" "encoding/json" + "fmt" + "github.com/mitchellh/mapstructure" "github.com/stytchauth/stytch-go/v11/stytch" "github.com/stytchauth/stytch-go/v11/stytch/b2b/passwords" "github.com/stytchauth/stytch-go/v11/stytch/stytcherror" @@ -107,24 +109,14 @@ func (c *PasswordsClient) Migrate( } // Authenticate a member with their email address and password. This endpoint verifies that the member has -// a password currently set, and that the entered password is correct. There are two instances where the -// endpoint will return a reset_password error even if they enter their previous password: -// * The member’s credentials appeared in the HaveIBeenPwned dataset. -// - We force a password reset to ensure that the member is the legitimate owner of the email address, +// a password currently set, and that the entered password is correct. // -// and not a malicious actor abusing the compromised credentials. -// * A member that has previously authenticated with email/password uses a passwordless authentication -// method tied to the same email address (e.g. Magic Links) for the first time. Any subsequent -// email/password authentication attempt will result in this error. -// - We force a password reset in this instance in order to safely deduplicate the account by email -// -// address, without introducing the risk of a pre-hijack account takeover attack. -// - Imagine a bad actor creates many accounts using passwords and the known email addresses of their -// -// victims. If a victim comes to the site and logs in for the first time with an email-based passwordless -// authentication method then both the victim and the bad actor have credentials to access to the same -// account. To prevent this, any further email/password login attempts first require a password reset which -// can only be accomplished by someone with access to the underlying email address. +// If you have breach detection during authentication enabled in your +// [password strength policy](https://stytch.com/docs/b2b/guides/passwords/strength-policies) and the +// member's credentials have appeared in the HaveIBeenPwned dataset, this endpoint will return a +// `member_reset_password` error even if the member enters a correct password. We force a password reset in +// this case to ensure that the member is the legitimate owner of the email address and not a malicious +// actor abusing the compromised credentials. // // If the Member is required to complete MFA to log in to the Organization, the returned value of // `member_authenticated` will be `false`, and an `intermediate_session_token` will be returned. @@ -159,3 +151,64 @@ func (c *PasswordsClient) Authenticate( ) return &retVal, err } + +// AuthenticateWithClaims fills in the claims pointer with custom claims from the response. +// Pass in a map with the types of values you're expecting so that this function can marshal +// the claims from the response. See ExampleClient_AuthenticateWithClaims_map, +// ExampleClient_AuthenticateWithClaims_struct for examples +func (c *PasswordsClient) AuthenticateWithClaims( + ctx context.Context, + body *passwords.AuthenticateParams, + claims any, +) (*passwords.AuthenticateResponse, error) { + var jsonBody []byte + var err error + if body != nil { + jsonBody, err = json.Marshal(body) + if err != nil { + return nil, stytcherror.NewClientLibraryError("error marshaling request body") + } + } + + b, err := c.C.RawRequest( + ctx, + "POST", + "/v1/b2b/passwords/authenticate", + nil, + jsonBody, + ) + if err != nil { + return nil, err + } + + // First extract the Stytch data. + var retVal passwords.AuthenticateResponse + if err := json.Unmarshal(b, &retVal); err != nil { + return nil, fmt.Errorf("unmarshal passwords.AuthenticateResponse: %w", err) + } + + if claims == nil { + return &retVal, nil + } + + if m, ok := claims.(*map[string]any); ok { + *m = retVal.MemberSession.CustomClaims + return &retVal, nil + } + + // This is where we need to convert claims into a claimsMap + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &claims, + TagName: "json", + }) + if err != nil { + return nil, err + } + + err = decoder.Decode(retVal.MemberSession.CustomClaims) + if err != nil { + return nil, err + } + + return &retVal, err +} diff --git a/stytch/b2b/sessions.go b/stytch/b2b/sessions.go index 5d79959..d2911fe 100644 --- a/stytch/b2b/sessions.go +++ b/stytch/b2b/sessions.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" + "github.com/mitchellh/mapstructure" "github.com/stytchauth/stytch-go/v11/stytch" "github.com/stytchauth/stytch-go/v11/stytch/b2b/sessions" "github.com/stytchauth/stytch-go/v11/stytch/stytcherror" @@ -81,6 +82,67 @@ func (c *SessionsClient) Authenticate( return &retVal, err } +// AuthenticateWithClaims fills in the claims pointer with custom claims from the response. +// Pass in a map with the types of values you're expecting so that this function can marshal +// the claims from the response. See ExampleClient_AuthenticateWithClaims_map, +// ExampleClient_AuthenticateWithClaims_struct for examples +func (c *SessionsClient) AuthenticateWithClaims( + ctx context.Context, + body *sessions.AuthenticateParams, + claims any, +) (*sessions.AuthenticateResponse, error) { + var jsonBody []byte + var err error + if body != nil { + jsonBody, err = json.Marshal(body) + if err != nil { + return nil, stytcherror.NewClientLibraryError("error marshaling request body") + } + } + + b, err := c.C.RawRequest( + ctx, + "POST", + "/v1/b2b/sessions/authenticate", + nil, + jsonBody, + ) + if err != nil { + return nil, err + } + + // First extract the Stytch data. + var retVal sessions.AuthenticateResponse + if err := json.Unmarshal(b, &retVal); err != nil { + return nil, fmt.Errorf("unmarshal sessions.AuthenticateResponse: %w", err) + } + + if claims == nil { + return &retVal, nil + } + + if m, ok := claims.(*map[string]any); ok { + *m = retVal.MemberSession.CustomClaims + return &retVal, nil + } + + // This is where we need to convert claims into a claimsMap + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &claims, + TagName: "json", + }) + if err != nil { + return nil, err + } + + err = decoder.Decode(retVal.MemberSession.CustomClaims) + if err != nil { + return nil, err + } + + return &retVal, err +} + // Revoke a Session and immediately invalidate all its tokens. To revoke a specific Session, pass either // the `member_session_id`, `session_token`, or `session_jwt`. To revoke all Sessions for a Member, pass // the `member_id`. diff --git a/stytch/b2b/sso.go b/stytch/b2b/sso.go index 25283fd..ba8f3bb 100644 --- a/stytch/b2b/sso.go +++ b/stytch/b2b/sso.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" + "github.com/mitchellh/mapstructure" "github.com/stytchauth/stytch-go/v11/stytch" "github.com/stytchauth/stytch-go/v11/stytch/b2b/sso" "github.com/stytchauth/stytch-go/v11/stytch/stytcherror" @@ -106,3 +107,64 @@ func (c *SSOClient) Authenticate( ) return &retVal, err } + +// AuthenticateWithClaims fills in the claims pointer with custom claims from the response. +// Pass in a map with the types of values you're expecting so that this function can marshal +// the claims from the response. See ExampleClient_AuthenticateWithClaims_map, +// ExampleClient_AuthenticateWithClaims_struct for examples +func (c *SSOClient) AuthenticateWithClaims( + ctx context.Context, + body *sso.AuthenticateParams, + claims any, +) (*sso.AuthenticateResponse, error) { + var jsonBody []byte + var err error + if body != nil { + jsonBody, err = json.Marshal(body) + if err != nil { + return nil, stytcherror.NewClientLibraryError("error marshaling request body") + } + } + + b, err := c.C.RawRequest( + ctx, + "POST", + "/v1/b2b/sso/authenticate", + nil, + jsonBody, + ) + if err != nil { + return nil, err + } + + // First extract the Stytch data. + var retVal sso.AuthenticateResponse + if err := json.Unmarshal(b, &retVal); err != nil { + return nil, fmt.Errorf("unmarshal sso.AuthenticateResponse: %w", err) + } + + if claims == nil { + return &retVal, nil + } + + if m, ok := claims.(*map[string]any); ok { + *m = retVal.MemberSession.CustomClaims + return &retVal, nil + } + + // This is where we need to convert claims into a claimsMap + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &claims, + TagName: "json", + }) + if err != nil { + return nil, err + } + + err = decoder.Decode(retVal.MemberSession.CustomClaims) + if err != nil { + return nil, err + } + + return &retVal, err +} diff --git a/stytch/consumer/magiclinks/types.go b/stytch/consumer/magiclinks/types.go index d5fecf5..315088a 100644 --- a/stytch/consumer/magiclinks/types.go +++ b/stytch/consumer/magiclinks/types.go @@ -14,7 +14,13 @@ import ( // AuthenticateParams: Request type for `MagicLinks.Authenticate`. type AuthenticateParams struct { - // Token: The token to authenticate. + // Token: The Magic Link `token` from the `?token=` query parameter in the URL. + // + // The redirect URL will look like + // `https://example.com/authenticate?stytch_token_type=magic_links&token=rM_kw42CWBhsHLF62V75jELMbvJ87njMe3tFVj7Qupu7` + // + // In the redirect URL, the `stytch_token_type` will be `magic_link`. See + // [here](https://stytch.com/docs/guides/dashboard/redirect-urls) for more detail. Token string `json:"token,omitempty"` // Attributes: Provided attributes help with fraud detection. Attributes *attribute.Attributes `json:"attributes,omitempty"` diff --git a/stytch/consumer/magiclinks_email.go b/stytch/consumer/magiclinks_email.go index 5ec0c8e..a57aefa 100644 --- a/stytch/consumer/magiclinks_email.go +++ b/stytch/consumer/magiclinks_email.go @@ -32,8 +32,10 @@ func NewMagicLinksEmailClient(c stytch.Client) *MagicLinksEmailClient { // ### Add an email to an existing user // This endpoint also allows you to add a new email address to an existing Stytch User. Including a // `user_id`, `session_token`, or `session_jwt` in your Send Magic Link by email request will add the new, -// unverified email address to the existing Stytch User. Upon successful authentication, the email address -// will be marked as verified. +// unverified email address to the existing Stytch User. If the user successfully authenticates within 5 +// minutes, the new email address will be marked as verified and remain permanently on the existing Stytch +// User. Otherwise, it will be removed from the User object, and any subsequent login requests using that +// email address will create a new User. // // ### Next steps // The user is emailed a magic link which redirects them to the provided @@ -73,8 +75,8 @@ func (c *MagicLinksEmailClient) Send( // // ### Next steps // The User is emailed a Magic Link which redirects them to the provided -// [redirect URL](https://stytch.com/docs/magic-links#email-magic-links_redirect-routing). Collect the -// `token` from the URL query parameters and call +// [redirect URL](https://stytch.com/docs/guides/magic-links/email-magic-links/redirect-routing). Collect +// the `token` from the URL query parameters and call // [Authenticate Magic Link](https://stytch.com/docs/api/authenticate-magic-link) to complete // authentication. func (c *MagicLinksEmailClient) LoginOrCreate( @@ -107,8 +109,8 @@ func (c *MagicLinksEmailClient) LoginOrCreate( // // ### Next steps // The User is emailed a Magic Link which redirects them to the provided -// [redirect URL](https://stytch.com/docs/magic-links#email-magic-links_redirect-routing). Collect the -// `token` from the URL query parameters and call +// [redirect URL](https://stytch.com/docs/guides/magic-links/email-magic-links/redirect-routing). Collect +// the `token` from the URL query parameters and call // [Authenticate Magic Link](https://stytch.com/docs/api/authenticate-magic-link) to complete // authentication. func (c *MagicLinksEmailClient) Invite( diff --git a/stytch/consumer/oauth/types.go b/stytch/consumer/oauth/types.go index 9fdf300..88d4534 100644 --- a/stytch/consumer/oauth/types.go +++ b/stytch/consumer/oauth/types.go @@ -27,7 +27,13 @@ type AttachParams struct { // AuthenticateParams: Request type for `OAuth.Authenticate`. type AuthenticateParams struct { - // Token: The token to authenticate. + // Token: The OAuth `token` from the `?token=` query parameter in the URL. + // + // The redirect URL will look like + // `https://example.com/authenticate?stytch_token_type=oauth&token=rM_kw42CWBhsHLF62V75jELMbvJ87njMe3tFVj7Qupu7` + // + // In the redirect URL, the `stytch_token_type` will be `oauth`. See + // [here](https://stytch.com/docs/guides/dashboard/redirect-urls) for more detail. Token string `json:"token,omitempty"` // SessionToken: Reuse an existing session instead of creating a new one. If you provide us with a // `session_token`, then we'll update the session represented by this session token with this OAuth factor. diff --git a/stytch/consumer/otp_email.go b/stytch/consumer/otp_email.go index bb7f9bb..fa9f864 100644 --- a/stytch/consumer/otp_email.go +++ b/stytch/consumer/otp_email.go @@ -32,8 +32,10 @@ func NewOTPsEmailClient(c stytch.Client) *OTPsEmailClient { // ### Add an email to an existing user // This endpoint also allows you to add a new email address to an existing Stytch User. Including a // `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by email request will add -// the new, unverified email address to the existing Stytch User. Upon successful authentication, the email -// address will be marked as verified. +// the new, unverified email address to the existing Stytch User. If the user successfully authenticates +// within 5 minutes, the new email address will be marked as verified and remain permanently on the +// existing Stytch User. Otherwise, it will be removed from the User object, and any subsequent login +// requests using that email address will create a new User. // // ### Next steps // Collect the OTP which was delivered to the user. Call diff --git a/stytch/consumer/otp_sms.go b/stytch/consumer/otp_sms.go index 426d935..2b5ce45 100644 --- a/stytch/consumer/otp_sms.go +++ b/stytch/consumer/otp_sms.go @@ -40,8 +40,10 @@ func NewOTPsSmsClient(c stytch.Client) *OTPsSmsClient { // // This endpoint also allows you to add a new phone number to an existing Stytch User. Including a // `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by SMS request will add the -// new, unverified phone number to the existing Stytch User. Upon successful authentication, the phone -// number will be marked as verified. +// new, unverified phone number to the existing Stytch User. If the user successfully authenticates within +// 5 minutes, the new phone number will be marked as verified and remain permanently on the existing Stytch +// User. Otherwise, it will be removed from the User object, and any subsequent login requests using that +// phone number will create a new User. // // ### Next steps // diff --git a/stytch/consumer/otp_whatsapp.go b/stytch/consumer/otp_whatsapp.go index 2623de4..aacdfa1 100644 --- a/stytch/consumer/otp_whatsapp.go +++ b/stytch/consumer/otp_whatsapp.go @@ -40,8 +40,10 @@ func NewOTPsWhatsappClient(c stytch.Client) *OTPsWhatsappClient { // // This endpoint also allows you to add a new phone number to an existing Stytch User. Including a // `user_id`, `session_token`, or `session_jwt` in your Send one-time passcode by WhatsApp request will add -// the new, unverified phone number to the existing Stytch User. Upon successful authentication, the phone -// number will be marked as verified. +// the new, unverified phone number to the existing Stytch User. If the user successfully authenticates +// within 5 minutes, the new phone number will be marked as verified and remain permanently on the existing +// Stytch User. Otherwise, it will be removed from the User object, and any subsequent login requests using +// that phone number will create a new User. // // ### Next steps // diff --git a/stytch/consumer/passwords/email/types.go b/stytch/consumer/passwords/email/types.go index 30ded3b..41d8496 100644 --- a/stytch/consumer/passwords/email/types.go +++ b/stytch/consumer/passwords/email/types.go @@ -15,7 +15,12 @@ import ( // ResetParams: Request type for `Email.Reset`. type ResetParams struct { - // Token: The token to authenticate. + // Token: The Passwords `token` from the `?token=` query parameter in the URL. + // + // In the redirect URL, the `stytch_token_type` will be `login` or `reset_password`. + // + // See examples and read more about redirect URLs + // [here](https://stytch.com/docs/guides/dashboard/redirect-urls). Token string `json:"token,omitempty"` // Password: The password of the user Password string `json:"password,omitempty"` diff --git a/stytch/consumer/passwords_email.go b/stytch/consumer/passwords_email.go index 0f16cbd..97933c2 100644 --- a/stytch/consumer/passwords_email.go +++ b/stytch/consumer/passwords_email.go @@ -59,6 +59,8 @@ func (c *PasswordsEmailClient) ResetStart( // The provided password needs to meet our password strength requirements, which can be checked in advance // with the password strength endpoint. If the token and password are accepted, the password is securely // stored for future authentication and the user is authenticated. +// +// Note that a successful password reset by email will revoke all active sessions for the `user_id`. func (c *PasswordsEmailClient) Reset( ctx context.Context, body *email.ResetParams, diff --git a/stytch/consumer/passwords_existingpassword.go b/stytch/consumer/passwords_existingpassword.go index 77cd8ca..6ea2b18 100644 --- a/stytch/consumer/passwords_existingpassword.go +++ b/stytch/consumer/passwords_existingpassword.go @@ -26,6 +26,9 @@ func NewPasswordsExistingPasswordClient(c stytch.Client) *PasswordsExistingPassw } // Reset the User’s password using their existing password. +// +// Note that a successful password reset via an existing password will revoke all active sessions for the +// `user_id`. func (c *PasswordsExistingPasswordClient) Reset( ctx context.Context, body *existingpassword.ResetParams, diff --git a/stytch/consumer/passwords_session.go b/stytch/consumer/passwords_session.go index 68dd158..40d56b2 100644 --- a/stytch/consumer/passwords_session.go +++ b/stytch/consumer/passwords_session.go @@ -29,6 +29,9 @@ func NewPasswordsSessionsClient(c stytch.Client) *PasswordsSessionsClient { // have a password, email magic link, or email OTP authentication factor that has been issued within the // last 5 minutes. This endpoint requires either a `session_jwt` or `session_token` be included in the // request. +// +// Note that a successful password reset via an existing session will revoke all active sessions for the +// `user_id`, except for the one used during the reset flow. func (c *PasswordsSessionsClient) Reset( ctx context.Context, body *session.ResetParams, diff --git a/stytch/consumer/sessions/types.go b/stytch/consumer/sessions/types.go index 3bc2c01..16986d5 100644 --- a/stytch/consumer/sessions/types.go +++ b/stytch/consumer/sessions/types.go @@ -278,7 +278,7 @@ type Session struct { SessionID string `json:"session_id,omitempty"` // UserID: The unique ID of the affected User. UserID string `json:"user_id,omitempty"` - // AuthenticationFactors: An array of different authentication factors that have initiated a Session. + // AuthenticationFactors: An array of different authentication factors that comprise a Session. AuthenticationFactors []AuthenticationFactor `json:"authentication_factors,omitempty"` // StartedAt: The timestamp when the Session was created. Values conform to the RFC 3339 standard and are // expressed in UTC, e.g. `2021-12-29T12:33:09Z`. diff --git a/stytch/consumer/users.go b/stytch/consumer/users.go index e464f00..eb58a60 100644 --- a/stytch/consumer/users.go +++ b/stytch/consumer/users.go @@ -101,9 +101,11 @@ func (c *UsersClient) Search( // **Note:** In order to add a new email address or phone number to an existing User object, pass the new // email address or phone number into the respective `/send` endpoint for the authentication method of your // choice. If you specify the existing User's `user_id` while calling the `/send` endpoint, the new, -// unverified email address or phone number will be added to the existing User object. Upon successful -// authentication, the email address or phone number will be marked as verified. We require this process to -// guard against an account takeover vulnerability. +// unverified email address or phone number will be added to the existing User object. If the user +// successfully authenticates within 5 minutes of the `/send` request, the new email address or phone +// number will be marked as verified and remain permanently on the existing Stytch User. Otherwise, it will +// be removed from the User object, and any subsequent login requests using that phone number will create a +// new User. We require this process to guard against an account takeover vulnerability. func (c *UsersClient) Update( ctx context.Context, body *users.UpdateParams,