Skip to content

Commit

Permalink
fix: Include JWKS in B2B constructor too (#138)
Browse files Browse the repository at this point in the history
  • Loading branch information
max-stytch authored Aug 7, 2023
1 parent a6004c0 commit 5b969d6
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 49 deletions.
63 changes: 63 additions & 0 deletions stytch/b2b/b2bstytchapi/b2bstytchapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ package b2bstytchapi

import (
"context"
"fmt"
"net/http"
"time"

"github.com/MicahParks/keyfunc/v2"
"github.com/stytchauth/stytch-go/v10/stytch"
"github.com/stytchauth/stytch-go/v10/stytch/b2b"
"github.com/stytchauth/stytch-go/v10/stytch/config"
Expand Down Expand Up @@ -123,6 +126,66 @@ func NewClient(projectID string, secret string, opts ...Option) (*API, error) {
a.Passwords = b2b.NewPasswordsClient(a.client)
a.SSO = b2b.NewSSOClient(a.client)
a.Sessions = b2b.NewSessionsClient(a.client)
// Set up JWKS for local session authentication
httpClient := defaultClient.HTTPClient
if realClient, ok := a.client.(*stytch.DefaultClient); ok {
httpClient = realClient.HTTPClient
}
jwks, err := a.instantiateJWKSClient(httpClient)
if err != nil {
return nil, fmt.Errorf("fetch JWKS from URL: %w", err)
}
a.M2M.JWKS = jwks

return a, nil
}

func (a *API) instantiateJWKSClient(httpClient *http.Client) (*keyfunc.JWKS, error) {
// The context given in the keyfunc Options applies throughout the lifetime of the JWKS
// fetcher. The context we were given here is _only_ for init, so we arrange to cancel the
// JWKS context manually if we couldn't start in time.
jwksCtx, jwksCancel := context.WithCancel(a.initializationContext)

jwkOptions := keyfunc.Options{
Client: httpClient,

// This is the context for ongoing background JWKS fetches. If the keyfunc starts in time,
// it should run until API.Close is called.
Ctx: jwksCtx,

RefreshErrorHandler: func(err error) {
if a.logger != nil {
a.logger.Printf("There was an error with the jwt.Keyfunc\nError: %s", err.Error())
}
},
RefreshInterval: time.Hour,
RefreshRateLimit: 5 * time.Minute,
RefreshTimeout: 10 * time.Second,
RefreshUnknownKID: true,
}

jwksURL := fmt.Sprintf("%s/v1/sessions/jwks/%s", a.baseURI, a.projectID)

type Res struct {
jwks *keyfunc.JWKS
err error
}
res := make(chan Res)
go func() {
jwks, err := keyfunc.Get(jwksURL, jwkOptions)
res <- Res{jwks, err}
}()

select {
case <-a.initializationContext.Done():
// Couldn't start in time, clean up the JWKS.
jwksCancel()
return nil, a.initializationContext.Err()
case res := <-res:
// JWKS setup finished first, _do not_ cancel its context. Let it continue fetching in the
// background.
_ = jwksCancel // lostcancel

return res.jwks, res.err
}
}
2 changes: 0 additions & 2 deletions stytch/b2b/mfa/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ package mfa
// or your changes may be overwritten later!
// !!!

// MemberOptions:
type MemberOptions struct {
// MFAPhoneNumber: The Member's MFA phone number.
MFAPhoneNumber string `json:"mfa_phone_number,omitempty"`
}

Expand Down
36 changes: 7 additions & 29 deletions stytch/b2b/organizations/members/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,16 @@ type CreateParams struct {
// for emergency purposes to gain access outside of normal authentication procedures. Refer to the
// [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for
// more details.
IsBreakglass bool `json:"is_breakglass,omitempty"`
// MFAPhoneNumber: (Coming Soon) The Member's phone number. A Member may only have one phone number.
IsBreakglass bool `json:"is_breakglass,omitempty"`
MFAPhoneNumber string `json:"mfa_phone_number,omitempty"`
// MFAEnrolled: (Coming Soon) Sets whether the Member is enrolled in MFA. If true, the Member must complete
// an MFA step whenever they wish to log in to their Organization. If false, the Member only needs to
// complete an MFA step if the Organization's MFA policy is set to `REQUIRED_FOR_ALL`.
MFAEnrolled bool `json:"mfa_enrolled,omitempty"`
}

// DeleteMFAPhoneNumberParams: Request type for `Members.DeleteMFAPhoneNumber`.
type DeleteMFAPhoneNumberParams struct {
// OrganizationID: Globally unique UUID that identifies a specific Organization. The `organization_id` is
// critical to perform operations on an Organization, so be sure to preserve this value.
OrganizationID string `json:"organization_id,omitempty"`
// MemberID: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform
// operations on a Member, so be sure to preserve this value.
MemberID string `json:"member_id,omitempty"`
MemberID string `json:"member_id,omitempty"`
}

// DeleteParams: Request type for `Members.Delete`.
Expand Down Expand Up @@ -134,11 +127,7 @@ type UpdateParams struct {
// for emergency purposes to gain access outside of normal authentication procedures. Refer to the
// [Organization object](organization-object) and its `auth_methods` and `allowed_auth_methods` fields for
// more details.
IsBreakglass bool `json:"is_breakglass,omitempty"`
// MFAPhoneNumber: (Coming Soon) Sets the Member's phone number. Throws an error if the Member already has
// a phone number. To change the Member's phone number, use the
// [Delete member phone number endpoint](https://stytch.com/docs/b2b/api/delete-member-mfa-phone-number) to
// delete the Member's existing phone number first.
IsBreakglass bool `json:"is_breakglass,omitempty"`
MFAPhoneNumber string `json:"mfa_phone_number,omitempty"`
// MFAEnrolled: (Coming Soon) Sets whether the Member is enrolled in MFA. If true, the Member must complete
// an MFA step whenever they wish to log in to their Organization. If false, the Member only needs to
Expand All @@ -163,23 +152,12 @@ type CreateResponse struct {
// are server errors.
StatusCode int32 `json:"status_code,omitempty"`
}

// DeleteMFAPhoneNumberResponse: Response type for `Members.DeleteMFAPhoneNumber`.
type DeleteMFAPhoneNumberResponse struct {
// RequestID: Globally unique UUID that is returned with every API call. This value is important to log for
// debugging purposes; we may ask for this value to help identify a specific API call when helping you
// debug an issue.
RequestID string `json:"request_id,omitempty"`
// MemberID: Globally unique UUID that identifies a specific Member.
MemberID string `json:"member_id,omitempty"`
// Member: The [Member object](https://stytch.com/docs/b2b/api/member-object).
Member organizations.Member `json:"member,omitempty"`
// Organization: The [Organization object](https://stytch.com/docs/b2b/api/organization-object).
RequestID string `json:"request_id,omitempty"`
MemberID string `json:"member_id,omitempty"`
Member organizations.Member `json:"member,omitempty"`
Organization organizations.Organization `json:"organization,omitempty"`
// StatusCode: The HTTP status code of the response. Stytch follows standard HTTP response status code
// patterns, e.g. 2XX values equate to success, 3XX values are redirects, 4XX are client errors, and 5XX
// are server errors.
StatusCode int32 `json:"status_code,omitempty"`
StatusCode int32 `json:"status_code,omitempty"`
}

// DeletePasswordResponse: Response type for `Members.DeletePassword`.
Expand Down
5 changes: 3 additions & 2 deletions stytch/b2b/organizations/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,10 @@ type Member struct {
// OrganizationID: Globally unique UUID that identifies a specific Organization. The `organization_id` is
// critical to perform operations on an Organization, so be sure to preserve this value.
OrganizationID string `json:"organization_id,omitempty"`
// MemberID: Globally unique UUID that identifies a specific Member.
// MemberID: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform
// operations on a Member, so be sure to preserve this value.
MemberID string `json:"member_id,omitempty"`
// EmailAddress: The email address.
// EmailAddress: The email address of the Member.
EmailAddress string `json:"email_address,omitempty"`
// Status: The status of the Member. The possible values are: `pending`, `invited`, `active`, or `deleted`.
Status string `json:"status,omitempty"`
Expand Down
1 change: 0 additions & 1 deletion stytch/b2b/organizations_members.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ func (c *OrganizationsMembersClient) Delete(
return &retVal, err
}

// DeleteMFAPhoneNumber: Delete a Member's MFA phone number.
func (c *OrganizationsMembersClient) DeleteMFAPhoneNumber(
ctx context.Context,
body *members.DeleteMFAPhoneNumberParams,
Expand Down
4 changes: 1 addition & 3 deletions stytch/b2b/otp/sms/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ type SendParams struct {
OrganizationID string `json:"organization_id,omitempty"`
// MemberID: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform
// operations on a Member, so be sure to preserve this value.
MemberID string `json:"member_id,omitempty"`
// MFAPhoneNumber: The phone number to send the OTP to. If the Member already has a phone number, this
// argument is not needed.
MemberID string `json:"member_id,omitempty"`
MFAPhoneNumber string `json:"mfa_phone_number,omitempty"`
// Locale: Used to determine which language to use when sending the user this delivery method. Parameter is
// a [IETF BCP 47 language tag](https://www.w3.org/International/articles/language-tags/), e.g. `"en"`.
Expand Down
13 changes: 3 additions & 10 deletions stytch/b2b/otp_sms.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,11 @@ func NewOTPsSmsClient(c stytch.Client) *OTPsSmsClient {

// Send a one-time passcode (OTP) to a Member's phone number. If the Member already has a phone number,
// this will send an OTP to the number associated with their `member_id`. If not, then this will send an
// OTP to the `mfa_phone_number` provided and link the `mfa_phone_number` with the Member.
// An error will be thrown if the Member already has a phone number and the provided `mfa_phone_number`
// does not match the existing one.
// OTP to the `phone_number` provided and link the `phone_number` with the Member.
// An error will be thrown if the Member already has a phone number and the provided `phone_number` does
// not match the existing one.
//
// Note that sending another OTP code before the first has expired will invalidate the first code.
//
// If a Member has a phone number and is enrolled in MFA, then after a successful primary authentication
// event (e.g. [email magic link](https://stytch.com/docs/b2b/api/authenticate-magic-link) or
// [SSO](https://stytch.com/docs/b2b/api/sso-authenticate) login is complete), an SMS OTP will
// automatically be sent to their phone number. In that case, this endpoint should only be used for
// subsequent authentication events, such as prompting a Member for an OTP again after a period of
// inactivity.
func (c *OTPsSmsClient) Send(
ctx context.Context,
body *sms.SendParams,
Expand Down
2 changes: 1 addition & 1 deletion stytch/config/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package config

const APIVersion = "10.1.0"
const APIVersion = "10.1.1"
3 changes: 2 additions & 1 deletion stytch/consumer/stytchapi/stytchapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,10 @@ func NewClient(projectID string, secret string, opts ...Option) (*API, error) {
if err != nil {
return nil, fmt.Errorf("fetch JWKS from URL: %w", err)
}
a.Sessions.JWKS = jwks
a.M2M.JWKS = jwks

a.Sessions.JWKS = jwks

return a, nil
}

Expand Down

0 comments on commit 5b969d6

Please sign in to comment.