Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix M2M authentication and add clock tolerance to JWT methods #130

Merged
merged 4 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ AllCops:
TargetRubyVersion: 2.7

Layout/LineLength: { Enabled: false }
LeadingCommentSpace: { Enabled: false }

Metrics: { Enabled: false }

Expand Down
5 changes: 3 additions & 2 deletions lib/stytch/b2b_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ def initialize(project_id:, secret:, env: nil, &block)
@api_host = api_host(env, project_id)
@project_id = project_id
@secret = secret
@is_b2b_client = true

create_connection(&block)

rbac = StytchB2B::RBAC.new(@connection)
@policy_cache = StytchB2B::PolicyCache.new(rbac_client: rbac)

@discovery = StytchB2B::Discovery.new(@connection)
@m2m = Stytch::M2M.new(@connection, @project_id)
@m2m = Stytch::M2M.new(@connection, @project_id, @is_b2b_client)
@magic_links = StytchB2B::MagicLinks.new(@connection)
@oauth = StytchB2B::OAuth.new(@connection)
@otps = StytchB2B::OTPs.new(@connection)
Expand All @@ -44,7 +45,7 @@ def initialize(project_id:, secret:, env: nil, &block)
@recovery_codes = StytchB2B::RecoveryCodes.new(@connection)
@scim = StytchB2B::SCIM.new(@connection)
@sso = StytchB2B::SSO.new(@connection)
@sessions = StytchB2B::Sessions.new(@connection, @project_id, @policy_cache)
@sessions = StytchB2B::Sessions.new(@connection, @project_id, @is_b2b_client, @policy_cache)
@totps = StytchB2B::TOTPs.new(@connection)
end

Expand Down
1 change: 0 additions & 1 deletion lib/stytch/b2b_magic_links.rb
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,6 @@ def login_or_signup(
# Send an invite email to a new Member to join an Organization. The Member will be created with an `invited` status until they successfully authenticate. Sending invites to `pending` Members will update their status to `invited`. Sending invites to already `active` Members will return an error.
#
# The magic link invite will be valid for 1 week.
# /%}
#
# == Parameters:
# organization_id::
Expand Down
48 changes: 5 additions & 43 deletions lib/stytch/b2b_organizations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,18 +230,6 @@ def get(
#
# *See the [Organization authentication settings](https://stytch.com/docs/b2b/api/org-auth-settings) resource to learn more about fields like `email_jit_provisioning`, `email_invites`, `sso_jit_provisioning`, etc., and their behaviors.
#
# Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in
# a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the
# Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields
# are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have
# permission to perform the `update.info.name` action on the `stytch.organization` Resource.
#
# If the Member Session does not contain a Role that satisfies the requested permissions, or if the Member's Organization
# does not match the `organization_id` passed in the request, a 403 error will be thrown. Otherwise, the request will
# proceed as normal.
#
# To learn more about our RBAC implementation, see our [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/overview).
#
# == Parameters:
# organization_id::
# 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.
Expand Down Expand Up @@ -429,7 +417,7 @@ def update(
put_request("/v1/b2b/organizations/#{organization_id}", request, headers)
end

# Deletes an Organization specified by `organization_id`. All Members of the Organization will also be deleted. /%}
# Deletes an Organization specified by `organization_id`. All Members of the Organization will also be deleted.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -673,18 +661,6 @@ def initialize(connection)

# Updates a Member specified by `organization_id` and `member_id`.
#
# Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in
# a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the
# Member Session has the necessary permissions. The specific permissions needed depend on which of the optional fields
# are passed in the request. For example, if the `organization_name` argument is provided, the Member Session must have
# permission to perform the `update.info.name` action on the `stytch.organization` Resource.
#
# If the Member Session does not contain a Role that satisfies the requested permissions, or if the Member's Organization
# does not match the `organization_id` passed in the request, a 403 error will be thrown. Otherwise, the request will
# proceed as normal.
#
# To learn more about our RBAC implementation, see our [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/overview).
#
# == Parameters:
# organization_id::
# 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.
Expand Down Expand Up @@ -806,7 +782,7 @@ def update(
put_request("/v1/b2b/organizations/#{organization_id}/members/#{member_id}", request, headers)
end

# Deletes a Member specified by `organization_id` and `member_id`. /%}
# Deletes a Member specified by `organization_id` and `member_id`.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -840,7 +816,7 @@ def delete(
delete_request("/v1/b2b/organizations/#{organization_id}/members/#{member_id}", headers)
end

# Reactivates a deleted Member's status and its associated email status (if applicable) to active, specified by `organization_id` and `member_id`. /%}
# Reactivates a deleted Member's status and its associated email status (if applicable) to active, specified by `organization_id` and `member_id`.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -889,7 +865,6 @@ def reactivate(
# Existing Member Sessions that include a phone number authentication factor will not be revoked if the phone number is deleted, and MFA will not be enforced until the Member logs in again.
# If you wish to enforce MFA immediately after a phone number is deleted, you can do so by prompting the Member to enter a new phone number
# and calling the [OTP SMS send](https://stytch.com/docs/b2b/api/otp-sms-send) endpoint, then calling the [OTP SMS Authenticate](https://stytch.com/docs/b2b/api/authenticate-otp-sms) endpoint.
# /%}
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -934,7 +909,6 @@ def delete_mfa_phone_number(
# To mint a new registration for a Member, you must first call this endpoint to delete the existing registration.
#
# Existing Member Sessions that include the TOTP authentication factor will not be revoked if the registration is deleted, and MFA will not be enforced until the Member logs in again.
# /%}
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -978,18 +952,6 @@ def delete_totp(
#
# *All fuzzy search filters require a minimum of three characters.
#
# Our RBAC implementation offers out-of-the-box handling of authorization checks for this endpoint. If you pass in
# a header containing a `session_token` or a `session_jwt` for an unexpired Member Session, we will check that the
# Member Session has permission to perform the `search` action on the `stytch.member` Resource. In addition, enforcing
# RBAC on this endpoint means that you may only search for Members within the calling Member's Organization, so the
# `organization_ids` argument may only contain the `organization_id` of the Member Session passed in the header.
#
# If the Member Session does not contain a Role that satisfies the requested permission, or if the `organization_ids`
# argument contains an `organization_id` that the Member Session does not belong to, a 403 error will be thrown.
# Otherwise, the request will proceed as normal.
#
# To learn more about our RBAC implementation, see our [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/overview).
#
# == Parameters:
# organization_ids::
# An array of organization_ids. At least one value is required.
Expand Down Expand Up @@ -1043,7 +1005,7 @@ def search(
post_request('/v1/b2b/organizations/members/search', request, headers)
end

# Delete a Member's password. /%}
# Delete a Member's password.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -1116,7 +1078,7 @@ def dangerously_get(
get_request(request, headers)
end

# Creates a Member. An `organization_id` and `email_address` are required. /%}
# Creates a Member. An `organization_id` and `email_address` are required.
#
# == Parameters:
# organization_id::
Expand Down
20 changes: 14 additions & 6 deletions lib/stytch/b2b_passwords.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def initialize(connection)
#
# == Parameters:
# password::
# The password to authenticate.
# The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
# The type of this field is +String+.
# email_address::
# The email address of the Member.
Expand Down Expand Up @@ -89,6 +89,8 @@ def strength_check(

# Adds an existing password to a member's email that doesn't have a password yet. We support migrating members from passwords stored with bcrypt, scrypt, argon2, MD-5, SHA-1, and PBKDF2. This endpoint has a rate limit of 100 requests per second.
#
# The member's email will be marked as verified when you use this endpoint.
#
# == Parameters:
# email_address::
# The email address of the Member.
Expand Down Expand Up @@ -219,7 +221,7 @@ def migrate(
# The email address of the Member.
# The type of this field is +String+.
# password::
# The password to authenticate.
# The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
# The type of this field is +String+.
# session_token::
# A secret token for a given Stytch Session.
Expand Down Expand Up @@ -427,12 +429,14 @@ def reset_start(
#
# If a valid `session_token` or `session_jwt` is passed in, the Member will not be required to complete an MFA step.
#
# Note that a successful password reset by email will revoke all active sessions for the `member_id`.
#
# == Parameters:
# password_reset_token::
# The password reset token to authenticate.
# The type of this field is +String+.
# password::
# The password to reset.
# The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
# The type of this field is +String+.
# session_token::
# Reuse an existing session instead of creating a new one. If you provide a `session_token`, Stytch will update the session.
Expand Down Expand Up @@ -557,12 +561,14 @@ def initialize(connection)

# Reset the Member's password using their existing session. The endpoint will error if the session does not contain an authentication factor that has been issued within the last 5 minutes. Either `session_token` or `session_jwt` should be provided.
#
# Note that a successful password reset via an existing session will revoke all active sessions for the `member_id`, except for the one used during the reset flow.
#
# == Parameters:
# organization_id::
# 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.
# The type of this field is +String+.
# password::
# The password to authenticate.
# The password to authenticate, reset, or set for the first time. Any UTF8 character is allowed, e.g. spaces, emojis, non-English characers, etc.
# The type of this field is +String+.
# session_token::
# A secret token for a given Stytch Session.
Expand Down Expand Up @@ -677,15 +683,17 @@ def initialize(connection)
#
# If a valid `session_token` or `session_jwt` is passed in, the Member will not be required to complete an MFA step.
#
# Note that a successful password reset via an existing password will revoke all active sessions for the `member_id`.
#
# == Parameters:
# email_address::
# The email address of the Member.
# The type of this field is +String+.
# existing_password::
# The member's current password that they supplied.
# The Member's current password that they supplied.
# The type of this field is +String+.
# new_password::
# The member's elected new password.
# The Member's elected new password.
# The type of this field is +String+.
# organization_id::
# 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.
Expand Down
80 changes: 73 additions & 7 deletions lib/stytch/b2b_scim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ def to_headers
end
end

class GetGroupsRequestOptions
# Optional authorization object.
# Pass in an active Stytch Member session token or session JWT and the request
# will be run using that member's permissions.
attr_accessor :authorization

def initialize(
authorization: nil
)
@authorization = authorization
end

def to_headers
headers = {}
headers.merge!(@authorization.to_headers) if authorization
headers
end
end

class CreateRequestOptions
# Optional authorization object.
# Pass in an active Stytch Member session token or session JWT and the request
Expand Down Expand Up @@ -159,7 +178,7 @@ def initialize(connection)
@connection = connection
end

# Update a SCIM Connection. /%}
# Update a SCIM Connection.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -210,7 +229,7 @@ def update(
put_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}", request, headers)
end

# Deletes a SCIM Connection. /%}
# Deletes a SCIM Connection.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -244,7 +263,7 @@ def delete(
delete_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}", headers)
end

# Start a SCIM token rotation. /%}
# Start a SCIM token rotation.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -280,7 +299,7 @@ def rotate_start(
post_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}/rotate/start", request, headers)
end

# Completes a SCIM token rotation. This will complete the current token rotation process and update the active token to be the new token supplied in the [start SCIM token rotation](https://stytch.com/docs/b2b/api/scim-rotate-token-start) response. /%}
# Completes a SCIM token rotation. This will complete the current token rotation process and update the active token to be the new token supplied in the [start SCIM token rotation](https://stytch.com/docs/b2b/api/scim-rotate-token-start) response.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -316,7 +335,7 @@ def rotate_complete(
post_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}/rotate/complete", request, headers)
end

# Cancel a SCIM token rotation. This will cancel the current token rotation process, keeping the original token active. /%}
# Cancel a SCIM token rotation. This will cancel the current token rotation process, keeping the original token active.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -352,7 +371,54 @@ def rotate_cancel(
post_request("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}/rotate/cancel", request, headers)
end

# Create a new SCIM Connection. /%}
# Gets a paginated list of all SCIM Groups associated with a given Connection.
#
# == Parameters:
# organization_id::
# 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.
# The type of this field is +String+.
# connection_id::
# The ID of the SCIM connection.
# The type of this field is +String+.
# cursor::
# The `cursor` field allows you to paginate through your results. Each result array is limited to 1000 results. If your query returns more than 1000 results, you will need to paginate the responses using the `cursor`. If you receive a response that includes a non-null `next_cursor` in the `results_metadata` object, repeat the search call with the `next_cursor` value set to the `cursor` field to retrieve the next page of results. Continue to make search calls until the `next_cursor` in the response is null.
# The type of this field is nilable +String+.
# limit::
# The number of search results to return per page. The default limit is 100. A maximum of 1000 results can be returned by a single search request. If the total size of your result set is greater than one page size, you must paginate the response. See the `cursor` field.
# The type of this field is nilable +Integer+.
#
# == Returns:
# An object with the following fields:
# scim_groups::
# A list of SCIM Connection Groups belonging to the connection.
# The type of this field is list of +SCIMGroup+ (+object+).
# status_code::
# (no documentation yet)
# The type of this field is +Integer+.
# next_cursor::
# The `next_cursor` string is returned when your search result contains more than one page of results. This value is passed into your next search call in the `cursor` field.
# The type of this field is nilable +String+.
#
# == Method Options:
# This method supports an optional +StytchB2B::SCIM::Connection::GetGroupsRequestOptions+ object which will modify the headers sent in the HTTP request.
def get_groups(
organization_id:,
connection_id:,
cursor: nil,
limit: nil,
method_options: nil
)
headers = {}
headers = headers.merge(method_options.to_headers) unless method_options.nil?
query_params = {
cursor: cursor,
limit: limit
}
request = request_with_query_params("/v1/b2b/scim/#{organization_id}/connection/#{connection_id}", query_params)
get_request(request, headers)
end

# Create a new SCIM Connection.
#
# == Parameters:
# organization_id::
Expand Down Expand Up @@ -394,7 +460,7 @@ def create(
post_request("/v1/b2b/scim/#{organization_id}/connection", request, headers)
end

# Get SCIM Connections. /%}
# Get SCIM Connections.
#
# == Parameters:
# organization_id::
Expand Down
Loading
Loading