Skip to content

Commit

Permalink
Major release 1.0.0 (#52)
Browse files Browse the repository at this point in the history
* Introduce HTTP client and base URL as configuration options (#37)

* Add config modules

* Start to build behavior for HTTP client

* Add `Castable` module to define behavior for casting or transforming data

* Handle response

* Add HTTP methods to client behavior

* Add `TeslaClient` implementation

* Add comment to prod.exs to fix `mix format`

* Fix casing for `WorkOS` namespace

* Add `@deprecated` to `WorkOS.API`

* Fix test config

* Validate config

* Fix linter

* Add module for structured error response

* Extract env variables to separate variables

* Add livebook example (#38)

* Add `ISSUE_TEMPLATE.md` (#34)

* Permit `expires_in` param when creating passwordless session (#35)

Co-authored-by: Mark Tran <[email protected]>

* Start to build behavior for HTTP client

* Add `Castable` module to define behavior for casting or transforming data

* Handle response

* Add HTTP methods to client behavior

* Add `TeslaClient` implementation

* Add comment to prod.exs to fix `mix format`

* Fix casing for `WorkOS` namespace

* Add `@deprecated` to `WorkOS.API`

* Fix test config

* Validate config

* Fix linter

* Add livebook file

---------

Co-authored-by: Jordan Mackie <[email protected]>
Co-authored-by: Mark Tran <[email protected]>

* Introduce new SSO module and tests structure (#39)

* Update `mix.exs`

* Add util module

* Add `Connection` struct

* Add `List` castable module

* Add `list_connections` method

* Add `delete_connection` method

* Add `get_connection` method

* Add draft for `get_authorization_url`

* Add `dialyxir` dependency

* Remove old SSO module

* Define logic for `get_authorization_url`

* Add `Profile` and `ProfileAndToken` response structs

* Add `get_profile` function

* Add basic layer for tests

* Add structs to `mix.exs`

* Fix guard on `get_authorization_url`

* Remove deprecated tests

* Add doc comments for parameter options of `get_authorization_url`

* Remove `Application.put_env` from `test_helper`

* Add draft test

* Fix extension of test files to `exs`

* Apply case for test config

* Define base URL for test

* Fix test

* Implement tests for `get_authorization_url`

* Define test for `get_profile_and_token`

* Implement tests for `get_profile_and_token`

* Implement tests for `get_profile`

* Implement test for `get_connection`

* Implement test for `list_connections`

* Rename `ClientMock` to `SSO.ClientMock`

* Implement test for `delete_connection`

* Fix return type for `delete_connection`

* Fix `mix credo` issues

* Update comment indentation

* Handle case where `get_authorization_url` is called without having the application config loaded

* Add validation for `redirect_uri`

* Include `client_id` on WorkOS Client

* Update livebook examples

* Fix connection struct

* Add `WorkOS.Empty`

* Remove `IO.inspect`

* Refactor `Organizations` module  (#44)

* Add skeleton for Organizations module

* Remove old `Organizations` module

* Add missing `object` key to connections domain

* Add response struct for organizations

* Implement functions for `Organization` module

* Add `create_organization` and `update_organization`

* Extract exceptions to separate modules

* Allow to call `list` functions without client and map args

* Implement tests for organizations

* Update Livebook examples

* Remove `Logger`

* Remove `WorkOS.Util` and fix timestamps

* Refactor `Portal` module (#45)

* Add basic modules

* Define function clauses

* Include implementation

* Add Portal link response struct

* Add portal client mock

* Implement tests

* Add example to Livebook

* Refactor `Webhooks` module (#46)

* Restructure modules

* Add `Event` struct to `mix.exs`

* Refactor `DirectorySync` module (#47)

* Add base modules

* Implement `get_directory`

* Implement `list_directories`

* Implement `delete_directory`

* Implement `Directory.User`

* Add tests for directory users

* Remove `WorkOS.Util`

* Add Livebook examples

* Rollback `Util` changes

* Remove `DateTime` from timestamps to match API reference

* Add missing `object` keys to response structs

* Remove WorkOS.Util

* Refactor `Passwordless` module (#48)

* Add base modules

* Implement functions

* Implement tests

* Remove `message` property

* Add `Events` module (#49)

* Add base modules

* Implement `list_events`

* Implement tests

* Add to Livebook

* Refactor `AuditLogs` module (#50)

* Add export response struct

* Add base modules

* Add implementation for `create_export`

* Add tests for `create_export`

* Add `get_export` method

* Add `add_event` method

* Add examples to Livebook

* Remove MFA module (#51)

* Add User Management API  (#53)

* Add draft for User Management module

* Add `Invitation` struct

* Add `User` struct

* Add User API methods

* Add methods from `Invitation` API

* Add `OrganizationMembership` API methods

* Add Password Reset API methods

* Add Email Verification API methods

* Add MFA response structs

* Add Multi-Factor API methods

* Add Magic Auth API methods

* Add Authentication API methods

* Add `get_authorization_url`

* Update Livebook

* Fixes `authorize` parameters

* Add Domain Verification API (#55)

* Remove legacy `WorkOS.API`

* Add `OrganizationDomain` struct

* Add Domain Verification API methods

* Add domain verification API methods

* Update README.md

* Format modules

* Fix `credo` issues

* Fix dialyzer issues

* Rename `DomainVerification` to `OrganizationDomains` (#56)

* Rename to `OrganizationDomains`

* Add to Livebook

* Remove `domain` option and add tests for error case

* Update workflow

* Bump to `1.0.0`

* Introduce Dialyzer artifacts

* Remove `:hackney`

* Update `elixirc_paths`

* Rollback `elixirc_paths`

* Import `ExUnit.Assertions` on mock files

* Update `plug_crypto`

* Update `jason`

* Remove support for oldest OTP version

* Update `dialyxir`

* Remove enum constants

* Update mock API keys

* Add deprecated `MFA` API module  (#58)

* Fix timestamp mapping

* Add deprecated response structs

* Add deprecated API

* Add tests

* Fix generate_link types

* Install `hackney`

* Fix query params

* Fix `list_groups` params

* Fix `send_session` route from Magic Link

* Pass `code` as keyword argument

* Fix `challenge_factor`

* Fix `get_organization` snippet from Livebook

* Add organization ID as parameter on `update_organization`

* Add `semaphore.yml`

* Fix `WorkOS.Empty` struct

* Fix `get_profile_and_token` snippet on Livebook

* Fix `get_profile` Livebook snippet

* Add experimental disclaimer back to README.md

* Rollback semaphore

* Rollback `elixir.yml` workflow due to branch policies

---------

Co-authored-by: Jordan Mackie <[email protected]>
Co-authored-by: Mark Tran <[email protected]>
  • Loading branch information
3 people authored Dec 20, 2023
1 parent 2c03fe0 commit e979826
Show file tree
Hide file tree
Showing 105 changed files with 7,442 additions and 2,212 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ jobs:
restore-keys: ${{ runner.os }}-mix-
- name: Install dependencies
run: mix deps.get
- name: Check format
run: mix format --check-formatted
- name: Check linter
run: mix credo --strict -a
- name: Run tests
Expand Down
101 changes: 101 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Continuous Integration

on:
push:
branches:
- master
- release/**

pull_request:

env:
MIX_ENV: test

jobs:
test:
name: Test (Elixir ${{ matrix.elixir }}, OTP ${{ matrix.otp }})

runs-on: ubuntu-20.04
strategy:
matrix:
# https://hexdocs.pm/elixir/compatibility-and-deprecations.html#compatibility-between-elixir-and-erlang-otp
include:
# Newest supported Elixir/Erlang pair.
- elixir: '1.15'
otp: '26.0'
lint: true
dialyzer: true

# One version before the last supported one.
- elixir: '1.14.5'
otp: '25.3'

steps:
- name: Check out this repository
uses: actions/checkout@v3

- name: Setup Elixir and Erlang
uses: erlef/setup-beam@v1
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}

# We need to manually restore and then save, so that we can save the "_build" directory
# *without* the Elixir compiled code in it.
- name: Restore Mix dependencies cache
uses: actions/cache/restore@v3
id: mix-deps-cache
with:
path: |
_build
deps
key: |
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
restore-keys: |
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-
- name: Install and compile Mix dependencies
if: steps.mix-deps-cache.outputs.cache-hit != 'true'
run: mix do deps.get, deps.compile

- name: Save Mix dependencies cache
uses: actions/cache/save@v3
if: steps.mix-deps-cache.outputs.cache-hit != 'true'
with:
path: |
_build
deps
key: |
${{ steps.mix-deps-cache.outputs.cache-primary-key }}
- name: Check formatting
if: matrix.lint
run: mix format --check-formatted

- name: Check compiler warnings
if: matrix.lint
run: mix compile --warnings-as-errors

- name: Run tests
run: mix test

- name: Retrieve PLT Cache
uses: actions/cache@v3
if: matrix.dialyzer
id: plt-cache
with:
path: plts
key: |
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
restore-keys: |
${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-plts-
- name: Create PLTs
if: steps.plt-cache.outputs.cache-hit != 'true' && matrix.dialyzer
run: |
mkdir -p plts
mix dialyzer --plt
- name: Run dialyzer
if: matrix.dialyzer
run: mix dialyzer --no-check --halt-exit-status
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
workos-*.tar


# Temporary files for e.g. tests
/tmp

# Dialyzer
/plts/*.plt
/plts/*.plt.hash
46 changes: 14 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,35 @@ Add this package to the list of dependencies in your `mix.exs` file:

```ex
def deps do
[{:workos, "~> 0.4.0"}]
[{:workos, "~> 1.0.0"}]
end
```
The hex package can be found here: https://hex.pm/packages/workos

## Configuration

The WorkOS API relies on two configuration parameters, the `client_id` and the `api_key`. There are two ways to configure these values with this package.

### Recommended Method
In your `config/config.exs` file you can set the `:client_id` and `:api_key` scoped to `:workos` to be used globally by default across the SDK:
### Configure WorkOS API key & client ID on your app config

```ex
config :workos,
client_id: "project_12345"
api_key: "sk_12345",
config :workos, WorkOS.Client,
api_key: "sk_example_123456789",
client_id: "client_123456789"
```

Ideally, you should use environment variables to store protected keys like your `:api_key` like so:

```ex
config :workos,
client_id: System.get_env("WORKOS_CLIENT_ID"),
api_key: System.get_env("WORKOS_API_KEY")
```
The only required config option is `:api_key` and `:client_id`.

### Opts Method
Alternatively, you can override or avoid using these globally configured variables by passing a `:api_key` or `:client_id` directly to SDK methods via the optional `opts` parameter available on all methods:
By default, this library uses [Tesla](https://github.com/elixir-tesla/tesla) but it can be replaced via the `:client` option, according to the `WorkOS.Client` module behavior.

```ex
WorkOS.SSO.get_authorization_url(%{
connection: "<Connection ID>",
redirect_uri: "https://workos.com"
}, [
client_id: "project_12345",
api_key: "sk_12345"
])
```
This is great if you need to switch client IDs on the fly.
###

## SDK Versioning

For our SDKs WorkOS follows a Semantic Versioning process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (I.e. 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades.

## More Information

* [Single Sign-On Guide](https://workos.com/docs/sso/guide)
* [Directory Sync Guide](https://workos.com/docs/directory-sync/guide)
* [Admin Portal Guide](https://workos.com/docs/admin-portal/guide)
* [Magic Link Guide](https://workos.com/docs/magic-link/guide)
- [User Management Guide](https://workos.com/docs/user-management)
- [Single Sign-On Guide](https://workos.com/docs/sso/guide)
- [Directory Sync Guide](https://workos.com/docs/directory-sync/guide)
- [Admin Portal Guide](https://workos.com/docs/admin-portal/guide)
- [Magic Link Guide](https://workos.com/docs/magic-link/guide)
- [Domain Verification Guide](https://workos.com/docs/domain-verification/guide)
3 changes: 3 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Config

import_config "#{Mix.env()}.exs"
5 changes: 5 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Config

config :workos, WorkOS.Client,
client_id: System.get_env("WORKOS_CLIENT_ID"),
api_key: System.get_env("WORKOS_API_KEY")
1 change: 1 addition & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This empty module is for configuration purposes
16 changes: 16 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Config

workos_api_key = System.get_env("WORKOS_API_KEY")
workos_client_id = System.get_env("WORKOS_CLIENT_ID")

case {workos_api_key, workos_client_id} do
{nil, nil} ->
config :tesla, adapter: Tesla.Mock

config :workos, WorkOS.Client,
api_key: "sk_example_123456789",
client_id: "client_123456789"

{api_key, client_id} ->
config :workos, WorkOS.Client, api_key: api_key, client_id: client_id
end
126 changes: 116 additions & 10 deletions lib/workos.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,123 @@
defmodule WorkOS do
@moduledoc """
Use the WorkOS module to authenticate your requests to the WorkOS API
Documentation for `WorkOS`.
"""

def host, do: Application.get_env(:workos, :host)
def base_url, do: "https://" <> Application.get_env(:workos, :host)
def adapter, do: Application.get_env(:workos, :adapter) || Tesla.Adapter.Hackney
@config_module WorkOS.Client

def api_key(opts \\ [])
def api_key(api_key: api_key), do: api_key
def api_key(_opts), do: Application.get_env(:workos, :api_key)
@type config() ::
list(
{:api_key, String.t()}
| {:client_id, String.t()}
| {:base_url, String.t()}
| {:client, atom()}
)

def client_id(opts \\ [])
def client_id(client_id: client_id), do: client_id
def client_id(_opts), do: Application.get_env(:workos, :client_id)
@doc """
Returns a WorkOS client.
Accepts a keyword list of config opts, though if omitted then it will attempt to load
them from the application environment.
"""
@spec client() :: WorkOS.Client.t()
@spec client(config()) :: WorkOS.Client.t()
def client(config \\ config()) do
WorkOS.Client.new(config)
end

@doc """
Loads config values from the application environment.
Config options are as follows:
```ex
config :workos, WorkOS.Client
api_key: "sk_123",
client_id: "project_123",
base_url: "https://api.workos.com",
client: WorkOs.Client.TeslaClient
```
The only required config option is `:api_key` and `:client_id`. If you would like to replace the
HTTP client used by WorkOS, configure the `:client` option. By default, this library
uses [Tesla](https://github.com/elixir-tesla/tesla), but changing it is as easy as
defining your own client module. See the `WorkOS.Client` module docs for more info.
"""
@spec config() :: config()
def config do
config =
Application.get_env(:workos, @config_module) ||
raise """
Missing client configuration for WorkOS.
Configure your WorkOS API key in one of your config files, for example:
config :workos, #{inspect(@config_module)}, api_key: "sk_123", client_id: "project_123"
"""

validate_config!(config)
end

@spec validate_config!(WorkOS.config()) :: WorkOS.config() | no_return()
defp validate_config!(config) do
Keyword.get(config, :api_key) ||
raise WorkOS.ApiKeyMissingError

Keyword.get(config, :client_id) ||
raise WorkOS.ClientIdMissingError

config
end

@doc """
Defines the WorkOS base API URL
"""
def default_base_url, do: "https://api.workos.com"

@doc """
Retrieves the WorkOS base URL from application config.
"""
@spec base_url() :: String.t()
def base_url do
case Application.get_env(:workos, @config_module) do
config when is_list(config) ->
Keyword.get(config, :base_url, default_base_url())

_ ->
default_base_url()
end
end

@doc """
Retrieves the WorkOS client ID from application config.
"""
@spec client_id() :: String.t()
def client_id do
case Application.get_env(:workos, @config_module) do
config when is_list(config) ->
Keyword.get(config, :client_id, nil)

_ ->
nil
end
end

@spec client_id(WorkOS.Client.t()) :: String.t()
def client_id(client) do
Map.get(client, :client_id)
end

@doc """
Retrieves the WorkOS API key from application config.
"""
@spec api_key() :: String.t()
def api_key do
WorkOS.config()
|> Keyword.get(:api_key)
end

@spec api_key(WorkOS.Client.t()) :: String.t()
def api_key(client) do
Map.get(client, :api_key)
end
end
Loading

0 comments on commit e979826

Please sign in to comment.