diff --git a/include/internal/doc.hrl b/include/internal/doc.hrl
new file mode 100644
index 0000000..2260b73
--- /dev/null
+++ b/include/internal/doc.hrl
@@ -0,0 +1,8 @@
+%% TODO: Remove the following macros as soon as only OTP >= 27 is supported.
+-if(?OTP_RELEASE >= 27).
+ -define(MODULEDOC(Str), -moduledoc(Str)).
+ -define(DOC(Str), -doc(Str)).
+-else.
+ -define(MODULEDOC(Str), -compile([])).
+ -define(DOC(Str), -compile([])).
+-endif.
diff --git a/mix.exs b/mix.exs
index e6b7af9..59224f3 100644
--- a/mix.exs
+++ b/mix.exs
@@ -18,7 +18,6 @@ defmodule Oidcc.Mixfile do
docs: &docs/0,
description: to_string(@props[:description]),
package: package(),
- aliases: [docs: ["compile", &edoc_chunks/1, "docs"]],
test_coverage: [ignore_modules: [Oidcc.RecordStruct]]
]
end
@@ -80,17 +79,4 @@ defmodule Oidcc.Mixfile do
assets: %{"assets" => "assets"}
]
end
-
- defp edoc_chunks(_args) do
- base_path = Path.dirname(__ENV__.file)
- doc_chunk_path = Application.app_dir(:oidcc, "doc")
-
- :ok =
- :edoc.application(:oidcc, String.to_charlist(base_path),
- doclet: :edoc_doclet_chunks,
- layout: :edoc_layout_chunks,
- preprocess: true,
- dir: String.to_charlist(doc_chunk_path)
- )
- end
end
diff --git a/src/oidcc.erl b/src/oidcc.erl
index a7b0476..817afc3 100644
--- a/src/oidcc.erl
+++ b/src/oidcc.erl
@@ -1,33 +1,32 @@
-%%%-------------------------------------------------------------------
-%% @doc OpenID Connect High Level Interface
-%%
-%%
Setup
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://accounts.google.com">>,
-%% name => {local, google_config_provider}
-%% }).
-%% '''
-%%
-%% (or via a `supervisor')
-%%
-%% See {@link oidcc_provider_configuration_worker} for details
-%%
-%% Global Configuration
-%%
-%%
-%% - `max_clock_skew' (default `0') - Maximum allowed clock skew for JWT
-%% `exp' / `nbf' validation, in seconds
-%%
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+OpenID Connect High Level Interface
+
+## Setup
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://accounts.google.com">>,
+ name => {local, google_config_provider}
+ }).
+```
+
+(or via a `m:supervisor`)
+
+See `m:oidcc_provider_configuration_worker` for details
+
+## Global Configuration
+
+* `max_clock_skew` (default `0`) - Maximum allowed clock skew for JWT
+ `exp` / `nbf` validation, in seconds
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-export([client_credentials_token/4]).
-export([create_redirect_url/4]).
-export([initiate_logout_url/4]).
@@ -37,24 +36,24 @@
-export([retrieve_token/5]).
-export([retrieve_userinfo/5]).
-%% @doc
-%% Create Auth Redirect URL
-%%
-%% Examples
-%%
-%% ```
-%% {ok, RedirectUri} =
-%% oidcc:create_redirect_url(
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>
-%% #{redirect_uri: <<"https://my.server/return"}
-%% ),
-%%
-%% %% RedirectUri = https://my.provider/auth?scope=openid&response_type=code&client_id=client_id&redirect_uri=https%3A%2F%2Fmy.server%2Freturn
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Auth Redirect URL
+
+## Examples
+
+```erlang
+{ok, RedirectUri} =
+ oidcc:create_redirect_url(
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>
+ #{redirect_uri: <<"https://my.server/return"}
+ ),
+
+%% RedirectUri = https://my.provider/auth?scope=openid&response_type=code&client_id=client_id&redirect_uri=https%3A%2F%2Fmy.server%2Freturn
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec create_redirect_url(
ProviderConfigurationWorkerName,
ClientId,
@@ -82,29 +81,29 @@ create_redirect_url(ProviderConfigurationWorkerName, ClientId, ClientSecret, Opt
oidcc_authorization:create_redirect_url(ClientContext, OtherOpts)
end.
-%% @doc
-%% retrieve the token using the authcode received before and directly validate
-%% the result.
-%%
-%% the authcode was sent to the local endpoint by the OpenId Connect provider,
-%% using redirects
-%%
-%% Examples
-%%
-%% ```
-%% %% Get AuthCode from Redirect
-%%
-%% {ok, #oidcc_token{}} =
-%% oidcc:retrieve_token(
-%% AuthCode,
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{redirect_uri => <<"https://example.com/callback">>}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Retrieve the token using the authcode received before and directly validate
+the result.
+
+The authcode was sent to the local endpoint by the OpenId Connect provider,
+using redirects.
+
+## Examples
+
+```erlang
+%% Get AuthCode from Redirect
+
+{ok, #oidcc_token{}} =
+ oidcc:retrieve_token(
+ AuthCode,
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{redirect_uri => <<"https://example.com/callback">>}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec retrieve_token(
AuthCode,
ProviderConfigurationWorkerName,
@@ -145,25 +144,25 @@ retrieve_token(
oidcc_token:retrieve(AuthCode, ClientContext, OptsWithRefresh)
end.
-%% @doc
-%% Load userinfo for the given token
-%%
-%% Examples
-%%
-%% ```
-%% %% Get Token
-%%
-%% {ok, #{<<"sub">> => Sub}} =
-%% oidcc:retrieve_userinfo(
-%% Token,
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Load userinfo for the given token.
+
+## Examples
+
+```erlang
+%% Get Token
+
+{ok, #{<<"sub">> => Sub}} =
+ oidcc:retrieve_userinfo(
+ Token,
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec retrieve_userinfo
(
Token,
@@ -208,24 +207,25 @@ retrieve_userinfo(
oidcc_userinfo:retrieve(Token, ClientContext, OtherOpts)
end.
-%% @doc Refresh Token
-%%
-%% Examples
-%%
-%% ```
-%% %% Get Token and wait for its expiry
-%%
-%% {ok, #oidcc_token{}} =
-%% oidcc:refresh_token(
-%% Token,
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{expected_subject => <<"sub_from_initial_id_token>>}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Refresh Token.
+
+## Examples
+
+```erlang
+%% Get Token and wait for its expiry
+
+{ok, #oidcc_token{}} =
+ oidcc:refresh_token(
+ Token,
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{expected_subject => <<"sub_from_initial_id_token">>}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec refresh_token
(
RefreshToken,
@@ -281,25 +281,25 @@ refresh_token(
oidcc_token:refresh(RefreshToken, ClientContext, OptsWithRefresh)
end.
-%% @doc
-%% Introspect the given access token
-%%
-%% Examples
-%%
-%% ```
-%% %% Get AccessToken
-%%
-%% {ok, #oidcc_token_introspection{active = True}} =
-%% oidcc:introspect_token(
-%% AccessToken,
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Introspect the given access token.
+
+## Examples
+
+```erlang
+%% Get AccessToken
+
+{ok, #oidcc_token_introspection{active = True}} =
+ oidcc:introspect_token(
+ AccessToken,
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec introspect_token(
Token,
ProviderConfigurationWorkerName,
@@ -336,32 +336,33 @@ introspect_token(
oidcc_token_introspection:introspect(Token, ClientContext, OtherOpts)
end.
-%% @doc Retrieve JSON Web Token (JWT) Profile Token
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc7523#section-4]
-%%
-%% Examples
-%%
-%% ```
-%% {ok, KeyJson} = file:read_file("jwt-profile.json"),
-%% KeyMap = jose:decode(KeyJson),
-%% Key = jose_jwk:from_pem(maps:get(<<"key">>, KeyMap)),
-%%
-%% {ok, #oidcc_token{}} =
-%% oidcc_token:jwt_profile(
-%% <<"subject">>,
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% Key,
-%% #{
-%% scope => [<<"scope">>],
-%% kid => maps:get(<<"keyId">>, KeyMap)
-%% }
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Retrieve JSON Web Token (JWT) Profile Token.
+
+See https://datatracker.ietf.org/doc/html/rfc7523#section-4.
+
+## Examples
+
+```erlang
+{ok, KeyJson} = file:read_file("jwt-profile.json"),
+KeyMap = jose:decode(KeyJson),
+Key = jose_jwk:from_pem(maps:get(<<"key">>, KeyMap)),
+
+{ok, #oidcc_token{}} =
+ oidcc_token:jwt_profile(
+ <<"subject">>,
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>,
+ Key,
+ #{
+ scope => [<<"scope">>],
+ kid => maps:get(<<"keyId">>, KeyMap)
+ }
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec jwt_profile_token(
Subject,
ProviderConfigurationWorkerName,
@@ -396,23 +397,24 @@ jwt_profile_token(Subject, ProviderConfigurationWorkerName, ClientId, ClientSecr
oidcc_token:jwt_profile(Subject, ClientContext, Jwk, OptsWithRefresh)
end.
-%% @doc Retrieve Client Credential Token
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4]
-%%
-%% Examples
-%%
-%% ```
-%% {ok, #oidcc_token{}} =
-%% oidcc:client_credentials_token(
-%% provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{scope => [<<"scope">>]}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Retrieve Client Credential Token.
+
+See https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4.
+
+## Examples
+
+```erlang
+{ok, #oidcc_token{}} =
+ oidcc:client_credentials_token(
+ provider_name,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{scope => [<<"scope">>]}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec client_credentials_token(
ProviderConfigurationWorkerName,
ClientId,
@@ -443,28 +445,28 @@ client_credentials_token(ProviderConfigurationWorkerName, ClientId, ClientSecret
oidcc_token:client_credentials(ClientContext, OptsWithRefresh)
end.
-%% @doc
-%% Create Initiate URI for Relaying Party initiated Logout
-%%
-%% See [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout]
-%%
-%% Examples
-%%
-%% ```
-%% %% Get `Token` from `oidcc_token`
-%%
-%% {ok, RedirectUri} =
-%% oidcc:initiate_logout_url(
-%% Token,
-%% provider_name,
-%% <<"client_id">>,
-%% #{post_logout_redirect_uri: <<"https://my.server/return"}
-%% ),
-%%
-%% %% RedirectUri = https://my.provider/logout?id_token_hint=IDToken&client_id=ClientId&post_logout_redirect_uri=https%3A%2F%2Fmy.server%2Freturn
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Initiate URI for Relaying Party initiated Logout.
+
+See https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout.
+
+## Examples
+
+```erlang
+%% Get `Token` from `oidcc_token`
+
+{ok, RedirectUri} =
+ oidcc:initiate_logout_url(
+ Token,
+ provider_name,
+ <<"client_id">>,
+ #{post_logout_redirect_uri: <<"https://my.server/return"}}
+ ).
+
+%% RedirectUri = https://my.provider/logout?id_token_hint=IDToken&client_id=ClientId&post_logout_redirect_uri=https%3A%2F%2Fmy.server%2Freturn
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec initiate_logout_url(
Token,
ProviderConfigurationWorkerName,
diff --git a/src/oidcc_auth_util.erl b/src/oidcc_auth_util.erl
index 460b6e9..b4338a1 100644
--- a/src/oidcc_auth_util.erl
+++ b/src/oidcc_auth_util.erl
@@ -1,11 +1,11 @@
-%%%-------------------------------------------------------------------
-%% @doc Authentication Utilities
-%% @end
-%%%-------------------------------------------------------------------
-module(oidcc_auth_util).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("Authentication Utilities").
+?MODULEDOC(#{since => <<"3.2.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
@@ -13,6 +13,7 @@
-export_type([auth_method/0, error/0]).
+?DOC(#{since => <<"3.2.0">>}).
-type auth_method() ::
none
| client_secret_basic
@@ -21,6 +22,7 @@
| private_key_jwt
| tls_client_auth.
+?DOC(#{since => <<"3.2.0">>}).
-type error() :: no_supported_auth_method.
-export([add_client_authentication/6]).
@@ -28,7 +30,7 @@
-export([add_authorization_header/6]).
-export([maybe_mtls_endpoint/4]).
-%% @private
+?DOC(false).
-spec add_client_authentication(
QueryList, Header, SupportedAuthMethods, AllowAlgorithms, Opts, ClientContext
) ->
@@ -276,7 +278,7 @@ add_jwt_bearer_assertion(ClientAssertion, Body, Header, ClientContext) ->
Header
}.
-%% @private
+?DOC(false).
-spec add_dpop_proof_header(Header, Method, Endpoint, Opts, ClientContext) -> Header when
Header :: [oidcc_http_util:http_header()],
Method :: post | get,
@@ -298,7 +300,7 @@ add_dpop_proof_header(Header, Method, Endpoint, Opts, ClientContext) ->
Header
end.
-%% @private
+?DOC(false).
-spec add_authorization_header(
AccessToken, AccessTokenType, Method, Endpoint, Opts, ClientContext
) ->
@@ -338,7 +340,7 @@ add_authorization_header(
[oidcc_http_util:bearer_auth_header(AccessToken)]
end.
-%% @private
+?DOC(false).
-spec maybe_mtls_endpoint(
Endpoint, auth_method(), MtlsEndpointName, ClientContext
) -> Endpoint when
diff --git a/src/oidcc_authorization.erl b/src/oidcc_authorization.erl
index bab2da0..64175b4 100644
--- a/src/oidcc_authorization.erl
+++ b/src/oidcc_authorization.erl
@@ -1,12 +1,11 @@
-%%%-------------------------------------------------------------------
-%% @doc Functions to start an OpenID Connect Authorization
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_authorization).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("Functions to start an OpenID Connect Authorization").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
@@ -17,6 +16,25 @@
-export_type([error/0]).
-export_type([opts/0]).
+?DOC("""
+Configure authorization redirect URL.
+
+See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest.
+
+## Parameters
+
+* `scopes` - list of scopes to request (defaults to `[<<"openid">>]`)
+* `state` - state to pass to the provider
+* `nonce` - nonce to pass to the provider
+* `purpose` - purpose of the authorization request, see [https://cdn.connectid.com.au/specifications/oauth2-purpose-01.html]
+* `require_purpose` - whether to require a `purpose` value
+* `pkce_verifier` - PKCE verifier (random string), see [https://datatracker.ietf.org/doc/html/rfc7636#section-4.1]
+* `require_pkce` - whether to require PKCE when getting the token
+* `redirect_uri` - redirect target after authorization is completed
+* `url_extension` - add custom query parameters to the authorization URL
+* `response_mode` - response mode to use (defaults to `<<"query">>`)
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
-type opts() ::
#{
scopes => oidcc_scope:scopes(),
@@ -30,27 +48,8 @@
url_extension => oidcc_http_util:query_params(),
response_mode => binary()
}.
-%% Configure authorization redirect url
-%%
-%% See [https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest]
-%%
-%% Parameters
-%%
-%%
-%% - `scopes' - list of scopes to request (defaults to `[<<"openid">>]')
-%% - `state' - state to pass to the provider
-%% - `nonce' - nonce to pass to the provider
-%% - `purpose' - purpose of the authorization request, see
-%% [https://cdn.connectid.com.au/specifications/oauth2-purpose-01.html]
-%% - `require_purpose' - whether to require a `purpose' value
-%% - `pkce_verifier' - pkce verifier (random string), see
-%% [https://datatracker.ietf.org/doc/html/rfc7636#section-4.1]
-%% - `require_pkce' - whether to require PKCE when getting the token
-%% - `redirect_uri' - redirect target after authorization is completed
-%% - `url_extension' - add custom query parameters to the authorization url
-%% - `response_mode' - response mode to use (defaults to `<<"query">>')
-%%
+?MODULEDOC(#{since => <<"3.0.0">>}).
-type error() ::
{grant_type_not_supported, authorization_code}
| par_required
@@ -81,28 +80,28 @@
metadata => <<"#{issuer => uri_string:uri_string(), client_id => binary()}">>
}).
-%% @doc
-%% Create Auth Redirect URL
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:create_redirect_url/4}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% {ok, RedirectUri} =
-%% oidcc_authorization:create_redirect_url(ClientContext,
-%% #{redirect_uri: <<"https://my.server/return"}),
-%%
-%% %% RedirectUri = https://my.provider/auth?scope=openid&response_type=code&client_id=client_id&redirect_uri=https%3A%2F%2Fmy.server%2Freturn
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Auth Redirect URL.
+
+For a high level interface using `m:oidcc_provider_configuration_worker`
+see `oidcc:create_redirect_url/4`.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+{ok, RedirectUri} =
+ oidcc_authorization:create_redirect_url(ClientContext,
+ #{redirect_uri: <<"https://my.server/return">}),
+
+%% RedirectUri = https://my.provider/auth?scope=openid&response_type=code&client_id=client_id&redirect_uri=https%3A%2F%2Fmy.server%2Freturn
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec create_redirect_url(ClientContext, Opts) -> {ok, Uri} | {error, error()} when
ClientContext :: oidcc_client_context:t(),
Opts :: opts(),
diff --git a/src/oidcc_backoff.erl b/src/oidcc_backoff.erl
index 85afd58..514f1dd 100644
--- a/src/oidcc_backoff.erl
+++ b/src/oidcc_backoff.erl
@@ -1,13 +1,14 @@
-%%%-------------------------------------------------------------------
-%% @doc Backoff Handling
-%%
-%% Based on `db_connection':
-%% [https://github.com/elixir-ecto/db_connection/blob/8ef1f2ea54922873590b8939f2dad6b031c5b49c/lib/db_connection/backoff.ex#L24]
-%% @end
-%% @since 3.2.0
-%%%-------------------------------------------------------------------
-module(oidcc_backoff).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+Backoff Handling
+
+Based on [`db_connection`](https://github.com/elixir-ecto/db_connection/blob/8ef1f2ea54922873590b8939f2dad6b031c5b49c/lib/db_connection/backoff.ex#L24)
+""").
+?MODULEDOC(#{since => <<"3.2.0">>}).
+
+
-export_type([type/0]).
-export_type([min/0]).
-export_type([max/0]).
@@ -15,15 +16,19 @@
-export([handle_retry/4]).
+?DOC(#{since => <<"3.2.0">>}).
-type type() :: stop | exponential | random | random_exponential.
+?DOC(#{since => <<"3.2.0">>}).
-type min() :: pos_integer().
+?DOC(#{since => <<"3.2.0">>}).
-type max() :: pos_integer().
+?DOC(#{since => <<"3.2.0">>}).
-opaque state() :: pos_integer() | {pos_integer(), pos_integer()}.
-%% @private
+?DOC(false).
-spec handle_retry(Type, Min, Max, State) -> stop | {Wait, State} when
Type :: type(), Min :: min(), Max :: max(), State :: undefined | state(), Wait :: pos_integer().
handle_retry(Type, Min, Max, State) when Min > 0, Max > 0, Max >= Min ->
diff --git a/src/oidcc_client_context.erl b/src/oidcc_client_context.erl
index 47e3490..ff3e250 100644
--- a/src/oidcc_client_context.erl
+++ b/src/oidcc_client_context.erl
@@ -1,24 +1,22 @@
-%%%-------------------------------------------------------------------
-%% @doc Client Configuration for authorization, token exchange and
-%% userinfo
-%%
-%% For most projects, it makes sense to use
-%% {@link oidcc_provider_configuration_worker} and the high-level
-%% interface of {@link oidcc}. In that case direct usage of this
-%% module is not needed.
-%%
-%% To use the record, import the definition:
-%%
-%% ```
-%% -include_lib(["oidcc/include/oidcc_client_context.hrl"]).
-%% '''
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_client_context).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+Client Configuration for authorization, token exchange, and userinfo.
+
+For most projects, it makes sense to use `m:oidcc_provider_configuration_worker` and the high-level
+interface of `oidcc`. In that case, direct usage of this module is not needed.
+
+To use the record, import the definition:
+
+```erlang
+-include_lib(["oidcc/include/oidcc_client_context.hrl"]).
+```
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
@@ -40,6 +38,7 @@
-type t() :: authenticated_t() | unauthenticated_t().
+?DOC(#{since => <<"3.0.0">>}).
-type authenticated_t() :: #oidcc_client_context{
provider_configuration :: oidcc_provider_configuration:t(),
jwks :: jose_jwk:key(),
@@ -48,6 +47,7 @@
client_jwks :: jose_jwk:key() | none
}.
+?DOC(#{since => <<"3.0.0">>}).
-type unauthenticated_t() :: #oidcc_client_context{
provider_configuration :: oidcc_provider_configuration:t(),
jwks :: jose_jwk:key(),
@@ -56,20 +56,26 @@
client_jwks :: none
}.
+?DOC(#{since => <<"3.0.0">>}).
-type authenticated_opts() :: #{
client_jwks => jose_jwk:key()
}.
+
+?DOC(#{since => <<"3.0.0">>}).
-type unauthenticated_opts() :: #{}.
+?DOC(#{since => <<"3.0.0">>}).
-type opts() :: authenticated_opts() | unauthenticated_opts().
+?DOC(#{since => <<"3.0.0">>}).
-type error() :: provider_not_ready.
-%% @doc Create Client Context from a {@link oidcc_provider_configuration_worker}
-%%
-%% See {@link from_configuration_worker/4}
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Client Context from a `m:oidcc_provider_configuration_worker`.
+
+See `from_configuration_worker/4`.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec from_configuration_worker
(ProviderName, ClientId, ClientSecret) -> {ok, authenticated_t()} | {error, error()} when
ProviderName :: gen_server:server_ref(),
@@ -82,39 +88,40 @@
from_configuration_worker(ProviderName, ClientId, ClientSecret) ->
from_configuration_worker(ProviderName, ClientId, ClientSecret, #{}).
-%% @doc Create Client Context from a {@link oidcc_provider_configuration_worker}
-%%
-%% Examples
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://login.salesforce.com">>
-%% }),
-%%
-%% {ok, #oidcc_client_context{}} =
-%% oidcc_client_context:from_configuration_worker(Pid,
-%% <<"client_id">>,
-%% <<"client_secret">>).
-%% '''
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://login.salesforce.com">>,
-%% name => {local, salesforce_provider}
-%% }),
-%%
-%% {ok, #oidcc_client_context{}} =
-%% oidcc_client_context:from_configuration_worker($
-%% salesforce_provider,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{client_jwks => jose_jwk:generate_key(16)}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Client Context from a `m:oidcc_provider_configuration_worker`.
+
+## Examples
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://login.salesforce.com">>
+ }),
+
+{ok, #oidcc_client_context{}} =
+ oidcc_client_context:from_configuration_worker(Pid,
+ <<"client_id">>,
+ <<"client_secret">>).
+```
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://login.salesforce.com">>,
+ name => {local, salesforce_provider}
+ }),
+
+{ok, #oidcc_client_context{}} =
+ oidcc_client_context:from_configuration_worker(
+ salesforce_provider,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{client_jwks => jose_jwk:generate_key(16)}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec from_configuration_worker
(ProviderName, ClientId, ClientSecret, Opts) ->
{ok, authenticated_t()} | {error, error()}
@@ -155,11 +162,12 @@ from_configuration_worker(ProviderName, ClientId, ClientSecret, Opts) ->
from_configuration_worker(Pid, ClientId, ClientSecret, Opts)
end.
-%% @doc Create Client Context manually
-%%
-%% See {@link from_manual/5}
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Client Context manually.
+
+See `from_manual/5`.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec from_manual
(Configuration, Jwks, ClientId, ClientSecret) -> authenticated_t() when
Configuration :: oidcc_provider_configuration:t(),
@@ -174,30 +182,30 @@ from_configuration_worker(ProviderName, ClientId, ClientSecret, Opts) ->
from_manual(Configuration, Jwks, ClientId, ClientSecret) ->
from_manual(Configuration, Jwks, ClientId, ClientSecret, #{}).
-%% @doc Create Client Context manually
-%%
-%% Examples
-%%
-%% ```
-%% {ok, Configuration} =
-%% oidcc_provider_configuration:load_configuration(<<"https://login.salesforce.com">>,
-%% []),
-%%
-%% #oidcc_provider_configuration{jwks_uri = JwksUri} = Configuration,
-%%
-%% {ok, Jwks} = oidcc_provider_configuration:load_jwks(JwksUri, []).
-%%
-%% #oidcc_client_context{} =
-%% oidcc_client_context:from_manual(
-%% Metadata,
-%% Jwks,
-%% <<"client_id">>,
-%% <<"client_secret">>,
-%% #{client_jwks => jose_jwk:generate_key(16)}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Create Client Context manually.
+
+## Examples
+
+```erlang
+{ok, Configuration} =
+ oidcc_provider_configuration:load_configuration(<<"https://login.salesforce.com">>, []),
+
+#oidcc_provider_configuration{jwks_uri = JwksUri} = Configuration,
+
+{ok, Jwks} = oidcc_provider_configuration:load_jwks(JwksUri, []).
+
+#oidcc_client_context{} =
+ oidcc_client_context:from_manual(
+ Metadata,
+ Jwks,
+ <<"client_id">>,
+ <<"client_secret">>,
+ #{client_jwks => jose_jwk:generate_key(16)}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec from_manual
(Configuration, Jwks, ClientId, ClientSecret, Opts) -> authenticated_t() when
Configuration :: oidcc_provider_configuration:t(),
@@ -239,33 +247,34 @@ from_manual(
client_jwks = maps:get(client_jwks, Opts, none)
}.
-%% @doc Apply OpenID Connect / OAuth2 Profiles to the context
-%%
-%% Currently, the only supported profiles are:
-%% - `fapi2_security_profile' - https://openid.bitbucket.io/fapi/fapi-2_0-security-profile.html
-%% - `fapi2_message_signing' - https://openid.bitbucket.io/fapi/fapi-2_0-message-signing.html
-%%
-%% It returns an updated `#oidcc_client_context{}' record and a map of options to
-%% be merged into the `oidcc_authorization` and `oidcc_token` functions.
-%%
-%% Examples
-%%
-%% ```
-%% ClientContext = #oidcc_client_context{} = oidcc_client_context:from_...(...),
-%%
-%% {#oidcc_client_context{} = ClientContext1, Opts} = oidcc_client_context:apply_profiles(
-%% ClientContext,
-%% #{
-%% profiles => [fapi2_message_signing]
-%% }),
-%%
-%% {ok, Uri} = oidcc_authorization:create_redirect_uri(
-%% ClientContext1,
-%% maps:merge(Opts, #{...})
-%% ).
-%% '''
-%% @end
-%% @since 3.2.0
+?DOC("""
+Apply OpenID Connect / OAuth2 Profiles to the context.
+
+Currently, the only supported profiles are:
+- `fapi2_security_profile` - https://openid.bitbucket.io/fapi/fapi-2_0-security-profile.html
+- `fapi2_message_signing` - https://openid.bitbucket.io/fapi/fapi-2_0-message-signing.html
+
+It returns an updated `t:t/0` record and a map of options to
+be merged into the `m:oidcc_authorization` and `m:oidcc_token` functions.
+
+## Examples
+
+```erlang
+ClientContext = #oidcc_client_context{} = oidcc_client_context:from_...(...),
+
+{#oidcc_client_context{} = ClientContext1, Opts} = oidcc_client_context:apply_profiles(
+ ClientContext,
+ #{
+ profiles => [fapi2_message_signing]
+ }),
+
+{ok, Uri} = oidcc_authorization:create_redirect_uri(
+ ClientContext1,
+ maps:merge(Opts, #{...})
+).
+```
+""").
+?DOC(#{since => <<"3.2.0">>}).
-spec apply_profiles(ClientContext, oidcc_profile:opts()) ->
{ok, ClientContext, oidcc_profile:opts_no_profiles()} | {error, oidcc_profile:error()}
when
diff --git a/src/oidcc_client_registration.erl b/src/oidcc_client_registration.erl
index 2c3d1f5..c516cef 100644
--- a/src/oidcc_client_registration.erl
+++ b/src/oidcc_client_registration.erl
@@ -1,26 +1,27 @@
-%%%-------------------------------------------------------------------
-%% @doc Dynamic Client Registration Utilities
-%%
-%% See [https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata]
-%%
-%% Records
-%%
-%% To use the record, import the definition:
-%%
-%% ```
-%% -include_lib(["oidcc/include/oidcc_client_registration.hrl"]).
-%% '''
-%%
-%% Telemetry
-%%
-%% See {@link 'Elixir.Oidcc.ClientRegistration'}
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_client_registration).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+Dynamic Client Registration Utilities.
+
+See https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata.
+
+## Records
+
+To use the record, import the definition:
+
+```erlang
+-include_lib(["oidcc/include/oidcc_client_registration.hrl"]).
+```
+
+## Telemetry
+
+See [`Oidcc.ClientRegistration`](`m:'Elixir.Oidcc.ClientRegistration'`).
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_registration.hrl").
-include("oidcc_provider_configuration.hrl").
@@ -31,19 +32,29 @@
-export_type([response/0]).
-export_type([t/0]).
+?DOC("""
+Configure configuration loading / parsing.
+
+## Parameters
+
+* `initial_access_token` - Access Token for registration
+* `request_opts` - config for HTTP request
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type opts() :: #{
initial_access_token => binary() | undefined,
request_opts => oidcc_http_util:request_opts()
}.
-% Configure configuration loading / parsing
-%
-% Parameters
-%
-%
-% - `initial_access_token' - Access Token for registration
-% - `request_opts' - config for HTTP request
-%
+?DOC("""
+Record containing Client Registration Metadata.
+
+See https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata and
+https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ClientMetadata.
+
+All unrecognized fields are stored in `extra_fields`.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type t() ::
#oidcc_client_registration{
%% OpenID Connect Dynamic Client Registration 1.0
@@ -115,13 +126,15 @@
%% Unknown Fields
extra_fields :: #{binary() => term()}
}.
-%% Record containing Client Registration Metadata
-%%
-%% See [https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata] and
-%% [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#ClientMetadata]
-%%
-%% All unrecognized fields are stored in `extra_fields'.
+?DOC("""
+Record containing Client Registration Response.
+
+See https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse.
+
+All unrecognized fields are stored in `extra_fields`.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type response() ::
#oidcc_client_registration_response{
client_id :: erlang:binary(),
@@ -133,12 +146,9 @@
%% Unknown Fields
extra_fields :: #{binary() => term()}
}.
-%% Record containing Client Registration Response
-%%
-%% See [https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse]
-%%
-%% All unrecognized fields are stored in `extra_fields'.
+
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
registration_not_supported
| invalid_content_type
@@ -166,28 +176,29 @@
metadata => <<"#{issuer => uri_string:uri_string()}">>
}).
-%% @doc Register Client
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ProviderConfiguration} =
-%% oidcc_provider_configuration:load_configuration("https://your.issuer"),
-%%
-%% {ok, #oidcc_client_registration_response{
-%% client_id = ClientId,
-%% client_secret = ClientSecret
-%% }} =
-%% oidcc_client_registration:register(
-%% ProviderConfiguration,
-%% #oidcc_client_registration{
-%% redirect_uris = ["https://your.application.com/oidcc/callback"]
-%% },
-%% #{initial_access_token => <<"optional token you got from the provider">>}
-%% ).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Register Client.
+
+## Examples
+
+```erlang
+{ok, ProviderConfiguration} =
+ oidcc_provider_configuration:load_configuration("https://your.issuer"),
+
+{ok, #oidcc_client_registration_response{
+ client_id = ClientId,
+ client_secret = ClientSecret
+}} =
+ oidcc_client_registration:register(
+ ProviderConfiguration,
+ #oidcc_client_registration{
+ redirect_uris = ["https://your.application.com/oidcc/callback"]
+ },
+ #{initial_access_token => <<"optional token you got from the provider">>}
+ ).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec register(ProviderConfiguration, Registration, Opts) ->
{ok, response()} | {error, error()}
when
diff --git a/src/oidcc_decode_util.erl b/src/oidcc_decode_util.erl
index 31bced0..de2b0f3 100644
--- a/src/oidcc_decode_util.erl
+++ b/src/oidcc_decode_util.erl
@@ -1,10 +1,9 @@
-%%%-------------------------------------------------------------------
-%% @doc Response Decoding Utils
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_decode_util).
+-include("internal/doc.hrl").
+?MODULEDOC("Response Decoding Utils").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-export([extract/3]).
-export([parse_setting_binary/2]).
-export([parse_setting_binary_list/2]).
@@ -18,6 +17,7 @@
-export_type([error/0]).
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
{missing_config_property, Key :: atom()}
| {invalid_config_property, {
@@ -34,7 +34,7 @@
Field :: atom()
}}.
-%% @private
+?DOC(false).
-spec extract(
Map :: #{binary() => term()},
Keys :: [{required, Key, ParseFn} | {optional, Key, Default, ParseFn}],
@@ -74,7 +74,7 @@ extract(Map1, [{optional, Key, Default, ParseFn} | RestKeys], Acc) ->
extract(Map, [], Acc) ->
{ok, {Acc, Map}}.
-%% @private
+?DOC(false).
-spec parse_setting_uri(Setting :: term(), Field :: atom()) ->
{ok, uri_string:uri_string()} | {error, error()}.
parse_setting_uri(Setting, _Field) when is_binary(Setting) ->
@@ -82,7 +82,7 @@ parse_setting_uri(Setting, _Field) when is_binary(Setting) ->
parse_setting_uri(_Setting, Field) ->
{error, {invalid_config_property, {uri, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_uri_https(Setting :: term(), Field :: atom()) ->
{ok, uri_string:uri_string()} | {error, error()}.
parse_setting_uri_https(Setting, Field) when is_binary(Setting) ->
@@ -95,13 +95,13 @@ parse_setting_uri_https(Setting, Field) when is_binary(Setting) ->
parse_setting_uri_https(_Setting, Field) ->
{error, {invalid_config_property, {uri_https, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_uri_map(Setting :: term(), Field :: atom()) ->
{ok, #{binary() => uri_string:uri_string()}} | {error, error()}.
parse_setting_uri_map(Setting, Field) ->
do_parse_setting_uri_map(Setting, Field, fun parse_setting_uri/2).
-%% @private
+?DOC(false).
-spec parse_setting_uri_https_map(Setting :: term(), Field :: atom()) ->
{ok, #{binary() => uri_string:uri_string()}} | {error, error()}.
parse_setting_uri_https_map(Setting, Field) ->
@@ -137,7 +137,7 @@ do_parse_setting_uri_map(#{} = Setting, Field, Parser) ->
do_parse_setting_uri_map(_Setting, Field, _Parser) ->
{error, {invalid_config_property, {uri_map, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_binary(Setting :: term(), Field :: atom()) ->
{ok, binary()} | {error, error()}.
parse_setting_binary(Setting, _Field) when is_binary(Setting) ->
@@ -145,7 +145,7 @@ parse_setting_binary(Setting, _Field) when is_binary(Setting) ->
parse_setting_binary(_Setting, Field) ->
{error, {invalid_config_property, {binary, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_binary_list(Setting :: term(), Field :: atom()) ->
{ok, [binary()]} | {error, error()}.
parse_setting_binary_list(Setting, Field) when is_list(Setting) ->
@@ -158,7 +158,7 @@ parse_setting_binary_list(Setting, Field) when is_list(Setting) ->
parse_setting_binary_list(_Setting, Field) ->
{error, {invalid_config_property, {list_of_binaries, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_number(Setting :: term(), Field :: atom()) ->
{ok, integer()} | {error, error()}.
parse_setting_number(Setting, _Field) when is_integer(Setting) ->
@@ -166,7 +166,7 @@ parse_setting_number(Setting, _Field) when is_integer(Setting) ->
parse_setting_number(_Setting, Field) ->
{error, {invalid_config_property, {number, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_boolean(Setting :: term(), Field :: atom()) ->
{ok, boolean()} | {error, error()}.
parse_setting_boolean(Setting, _Field) when is_boolean(Setting) ->
@@ -174,7 +174,7 @@ parse_setting_boolean(Setting, _Field) when is_boolean(Setting) ->
parse_setting_boolean(_Setting, Field) ->
{error, {invalid_config_property, {boolean, Field}}}.
-%% @private
+?DOC(false).
-spec parse_setting_list_enum(
Setting :: term(),
Field :: atom(),
diff --git a/src/oidcc_http_util.erl b/src/oidcc_http_util.erl
index 964f450..ddf4c7f 100644
--- a/src/oidcc_http_util.erl
+++ b/src/oidcc_http_util.erl
@@ -1,11 +1,10 @@
-%%%-------------------------------------------------------------------
-%% @doc HTTP Client Utilities
-%% @end
-%%%-------------------------------------------------------------------
-module(oidcc_http_util).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("HTTP Client Utilities").
+
-export([basic_auth_header/2]).
-export([bearer_auth_header/1]).
-export([headers_to_cache_deadline/2]).
@@ -15,38 +14,47 @@
http_header/0, error/0, httpc_error/0, query_params/0, telemetry_opts/0, request_opts/0
]).
+?DOC("See `uri_string:compose_query/1`.").
+?DOC(#{since => <<"3.0.0">>}).
-type query_params() :: [{unicode:chardata(), unicode:chardata() | true}].
-%% See {@link uri_string:compose_query/1}
+
+?DOC("See `httpc:request/5`.").
+?DOC(#{since => <<"3.0.0">>}).
-type http_header() :: {Field :: [byte()] | binary(), Value :: iodata()}.
-%% See {@link httpc:request/5}
+
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
{http_error, StatusCode :: pos_integer(), HttpBodyResult :: binary() | map()}
| {use_dpop_nonce, Nonce :: binary(), HttpBodyResult :: binary() | map()}
| invalid_content_type
| httpc_error().
+
+?DOC("See `httpc:request/5` for additional errors.").
+?DOC(#{since => <<"3.0.0">>}).
-type httpc_error() :: term().
-%% See {@link httpc:request/5} for additional errors
+?DOC("""
+See `httpc:request/5`.
+
+## Parameters
+
+* `timeout` - timeout for request
+* `ssl` - TLS config
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type request_opts() :: #{
timeout => timeout(),
ssl => [ssl:tls_option()],
httpc_profile => atom() | pid()
}.
-%% See {@link httpc:request/5}
-%%
-%% Parameters
-%%
-%%
-%% - `timeout' - timeout for request
-%% - `ssl' - TLS config
-%%
+?DOC(#{since => <<"3.0.0">>}).
-type telemetry_opts() :: #{
topic := [atom()],
extra_meta => map()
}.
-%% @private
+?DOC(false).
-spec basic_auth_header(User, Secret) -> http_header() when
User :: binary(),
Secret :: binary().
@@ -57,12 +65,12 @@ basic_auth_header(User, Secret) ->
AuthData = base64:encode(RawAuth),
{"authorization", [<<"Basic ">>, AuthData]}.
-%% @private
+?DOC(false).
-spec bearer_auth_header(Token) -> http_header() when Token :: binary().
bearer_auth_header(Token) ->
{"authorization", [<<"Bearer ">>, Token]}.
-%% @private
+?DOC(false).
-spec request(Method, Request, TelemetryOpts, RequestOpts) ->
{ok, {{json, term()} | {jwt, binary()}, [http_header()]}}
| {error, error()}
diff --git a/src/oidcc_jwt_util.erl b/src/oidcc_jwt_util.erl
index 4223d41..42da9e4 100644
--- a/src/oidcc_jwt_util.erl
+++ b/src/oidcc_jwt_util.erl
@@ -1,11 +1,10 @@
-%%%-------------------------------------------------------------------
-%% @doc JWT Utilities
-%% @end
-%%%-------------------------------------------------------------------
-module(oidcc_jwt_util).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("JWT Utilities").
+
-include_lib("jose/include/jose_jwe.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-include_lib("jose/include/jose_jws.hrl").
@@ -31,9 +30,11 @@
-export_type([error/0]).
-export_type([refresh_jwks_for_unknown_kid_fun/0]).
+?DOC(#{since => <<"3.0.0">>}).
-type refresh_jwks_for_unknown_kid_fun() ::
fun((Jwks :: jose_jwk:key(), Kid :: binary()) -> {ok, jose_jwk:key()} | {error, term()}).
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
no_matching_key
| invalid_jwt_token
@@ -42,18 +43,19 @@
| {none_alg_used, Jwt :: #jose_jwt{}, Jws :: #jose_jws{}}
| not_encrypted.
+?DOC(#{since => <<"3.0.0">>}).
-type claims() :: #{binary() => term()}.
-%% Function to decide if the jwks should be reladed to find a matching key for `Kid'
+%% Function to decide if the jwks should be reladed to find a matching key for `Kid`
%%
-%% A default function is provided in {@link oidcc:retrieve_token/5}
-%% and {@link oidcc:retrieve_userinfo/5}.
+%% A default function is provided in `oidcc:retrieve_token/5`
+%% and `oidcc:retrieve_userinfo/5`.
%%
%% The default implementation does not implement any rate limiting.
-
-%% @private
+%%
%% Checking of jwk sets is a bit wonky because of partial support
%% in jose. see: https://github.com/potatosalad/erlang-jose/issues/28
+?DOC(false).
-spec verify_signature(Token, AllowAlgorithms, Jwks) ->
{ok, {Jwt, Jws}}
| {error, error()}
@@ -111,7 +113,7 @@ verify_signature(Token, AllowAlgorithms, #jose_jwk{} = Jwks) ->
{error, invalid_jwt_token}
end.
-%% @private
+?DOC(false).
-spec verify_claims(Claims, ExpClaims) -> ok | {error, {missing_claim, ExpClaim, Claims}} when
Claims :: claims(),
ExpClaim :: {binary(), term()},
@@ -133,7 +135,7 @@ verify_claims(Claims, ExpClaims) ->
{error, {missing_claim, Claim, Claims}}
end.
-%% @private
+?DOC(false).
-spec client_secret_oct_keys(AllowedAlgorithms, ClientSecret) -> jose_jwk:key() | none when
AllowedAlgorithms :: [binary()] | undefined,
ClientSecret :: binary() | unauthenticated.
@@ -153,7 +155,7 @@ client_secret_oct_keys(AllowedAlgorithms, ClientSecret) ->
none
end.
-%% @private
+?DOC(false).
-spec merge_client_secret_oct_keys(Jwks :: jose_jwk:key(), AllowedAlgorithms, ClientSecret) ->
jose_jwk:key()
when
@@ -167,7 +169,7 @@ merge_client_secret_oct_keys(Jwks, AllowedAlgorithms, ClientSecret) ->
merge_jwks(Jwks, OctKeys)
end.
-%% @private
+?DOC(false).
-spec refresh_jwks_fun(ProviderConfigurationWorkerName) ->
refresh_jwks_for_unknown_kid_fun()
when
@@ -181,7 +183,7 @@ refresh_jwks_fun(ProviderConfigurationWorkerName) ->
{ok, oidcc_provider_configuration_worker:get_jwks(ProviderConfigurationWorkerName)}
end.
-%% @private
+?DOC(false).
-spec merge_jwks(Left :: jose_jwk:key(), Right :: jose_jwk:key()) -> jose_jwk:key().
merge_jwks(#jose_jwk{keys = {jose_jwk_set, LeftKeys}, fields = LeftFields}, #jose_jwk{
keys = {jose_jwk_set, RightKeys}, fields = RightFields
@@ -194,13 +196,13 @@ merge_jwks(#jose_jwk{} = Left, #jose_jwk{keys = {jose_jwk_set, _RightKeys}} = Ri
merge_jwks(Left, Right) ->
merge_jwks(Left, #jose_jwk{keys = {jose_jwk_set, [Right]}}).
-%% @private
+?DOC(false).
-spec sign(Jwt :: #jose_jwt{}, Jwk :: jose_jwk:key(), SupportedAlgorithms :: [binary()]) ->
{ok, binary()} | {error, no_supported_alg_or_key}.
sign(Jwt, Jwk, SupportedAlgorithms) ->
sign(Jwt, Jwk, SupportedAlgorithms, #{}).
-%% @private
+?DOC(false).
-spec sign(
Jwt :: #jose_jwt{}, Jwk :: jose_jwk:key(), SupportedAlgorithms :: [binary()], JwsFields :: map()
) ->
@@ -250,7 +252,7 @@ sign(Jwt, Jwk, [Algorithm | RestAlgorithms], JwsFields0) ->
_ -> sign(Jwt, Jwk, RestAlgorithms, JwsFields0)
end.
-%% private
+?DOC(false).
-spec decrypt_and_verify(
Jwt :: binary(),
Jwks :: jose_jwk:key(),
@@ -379,7 +381,7 @@ verify_decrypted_token(Jwt, SigningAlgs, Jwe, Jwks) ->
{error, Reason}
end.
-%% @private
+?DOC(false).
-spec encrypt(
Jwt :: binary(),
Jwk :: jose_jwk:key(),
@@ -435,7 +437,7 @@ encrypt(Jwt, Jwk, [Algorithm | _RestAlgorithms] = SupportedAlgorithms, Supported
error -> encrypt(Jwt, Jwk, SupportedAlgorithms, SupportedEncValues, RestEncValues)
end.
-%% @private
+?DOC(false).
-spec thumbprint(Jwk :: jose_jwk:key()) -> {ok, binary()} | error.
thumbprint(Jwk) ->
evaluate_for_all_keys(Jwk, fun
@@ -445,7 +447,7 @@ thumbprint(Jwk) ->
{ok, jose_jwk:thumbprint(Key)}
end).
-%% @private
+?DOC(false).
-spec sign_dpop(Jwt :: #jose_jwt{}, Jwk :: jose_jwk:key(), SigningAlgSupported :: [binary()]) ->
{ok, binary()} | {error, no_supported_alg_or_key}.
sign_dpop(Jwt, Jwk, SigningAlgSupported) ->
@@ -459,7 +461,7 @@ sign_dpop(Jwt, Jwk, SigningAlgSupported) ->
})
end).
-%% @private
+?DOC(false).
-spec evaluate_for_all_keys(Jwk :: jose_jwk:key(), fun((jose_jwk:key()) -> {ok, Result} | error)) ->
{ok, Result} | error
when
@@ -478,14 +480,14 @@ evaluate_for_all_keys(#jose_jwk{keys = {jose_jwk_set, Keys}}, Callback) ->
evaluate_for_all_keys(#jose_jwk{} = Jwk, Callback) ->
Callback(Jwk).
-%% @private
+?DOC(false).
-spec verify_not_none_alg(#jose_jws{}) -> ok | {error, none_alg_used}.
verify_not_none_alg(#jose_jws{fields = #{<<"alg">> := <<"none">>}}) ->
{error, none_alg_used};
verify_not_none_alg(#jose_jws{}) ->
ok.
-%% @private
+?DOC(false).
-spec peek_payload(binary()) -> {ok, #jose_jwt{}} | {error, invalid_jwt_token}.
peek_payload(Jwt) ->
try
diff --git a/src/oidcc_logout.erl b/src/oidcc_logout.erl
index 882b46a..140a279 100644
--- a/src/oidcc_logout.erl
+++ b/src/oidcc_logout.erl
@@ -1,12 +1,12 @@
-%%%-------------------------------------------------------------------
-%% @doc Logout from the OpenID Provider
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_logout).
-feature(maybe_expr, enable).
+
+-include("internal/doc.hrl").
+?MODULEDOC("Logout from the OpenID Provider.").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
-include("oidcc_token.hrl").
@@ -16,8 +16,23 @@
-export_type([error/0]).
-export_type([initiate_url_opts/0]).
+?DOC(#{since => <<"3.0.0">>}).
-type error() :: end_session_endpoint_not_supported.
+?DOC("""
+Configure Relaying Party initiated Logout URI.
+
+See https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout.
+
+## Parameters
+
+* `logout_hint` - logout_hint to pass to the provider
+* `post_logout_redirect_uri` - Post Logout Redirect URI to pass to the provider
+* `state` - state to pass to the provider
+* `ui_locales` - UI locales to pass to the provider
+* `extra_query_params` - extra query params to add to the URI
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type initiate_url_opts() :: #{
logout_hint => binary(),
post_logout_redirect_uri => uri_string:uri_string(),
@@ -25,51 +40,37 @@
ui_locales => binary(),
extra_query_params => oidcc_http_util:query_params()
}.
-%% Configure Relaying Party initiated Logout URI
-%%
-%% See [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout]
-%%
-%% Parameters
-%%
-%%
-%% - `logout_hint' - logout_hint to pass to the provider
-%% - `post_logout_redirect_uri' - Post Logout Redirect URI to pass to the
-%% provider
-%% - `state' - state to pass to the provider
-%% - `ui_locales' - UI locales to pass to the provider
-%% - `extra_query_params' - extra query params to add to the uri
-%%
-
-%% @doc
-%% Initiate URI for Relaying Party initiated Logout
-%%
-%% See [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout]
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:initiate_logout_url/4}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} = oidcc_client_context:from_configuration_worker(
-%% provider_name,
-%% <<"client_id">>,
-%% unauthenticated
-%% ),
-%%
-%% %% Get `Token` from `oidcc_token`
-%%
-%% {ok, RedirectUri} =
-%% oidcc_logout:initiate_url(
-%% Token,
-%% ClientContext,
-%% #{post_logout_redirect_uri: <<"https://my.server/return"}
-%% ),
-%%
-%% %% RedirectUri = https://my.provider/logout?id_token_hint=IDToken&client_id=ClientId&post_logout_redirect_uri=https%3A%2F%2Fmy.server%2Freturn
-%% '''
-%% @end
-%% @since 3.0.0
+
+?DOC("""
+Initiate URI for Relaying Party initiated Logout.
+
+See https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout.
+
+For a high level interface using `m:oidcc_provider_configuration_worker`
+see `oidcc:initiate_logout_url/4`.
+
+## Examples
+
+```erlang
+{ok, ClientContext} = oidcc_client_context:from_configuration_worker(
+ provider_name,
+ <<"client_id">>,
+ unauthenticated
+),
+
+%% Get `Token` from `oidcc_token`
+
+{ok, RedirectUri} =
+ oidcc_logout:initiate_url(
+ Token,
+ ClientContext,
+ #{post_logout_redirect_uri: <<"https://my.server/return">}
+),
+
+%% RedirectUri = https://my.provider/logout?id_token_hint=IDToken&client_id=ClientId&post_logout_redirect_uri=https%3A%2F%2Fmy.server%2Freturn
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec initiate_url(Token, ClientContext, Opts) ->
{ok, uri_string:uri_string()} | {error, error()}
when
diff --git a/src/oidcc_profile.erl b/src/oidcc_profile.erl
index ae9dc51..0eb6462 100644
--- a/src/oidcc_profile.erl
+++ b/src/oidcc_profile.erl
@@ -1,12 +1,11 @@
-%%%-------------------------------------------------------------------
-%% @doc OpenID Profile Utilities
-%% @end
-%% @since 3.2.0
-%%%-------------------------------------------------------------------
-module(oidcc_profile).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("OpenID Profile Utilities").
+?MODULEDOC(#{since => <<"3.2.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
@@ -17,8 +16,11 @@
-export_type([opts_no_profiles/0]).
-export_type([error/0]).
+?DOC(#{since => <<"3.2.0">>}).
-type profile() ::
mtls_constrain | fapi2_security_profile | fapi2_message_signing | fapi2_connectid_au.
+
+?DOC(#{since => <<"3.2.0">>}).
-type opts() :: #{
profiles => [profile()],
require_pkce => boolean(),
@@ -26,15 +28,19 @@
preferred_auth_methods => [oidcc_auth_util:auth_method()],
request_opts => oidcc_http_util:request_opts()
}.
+
+?DOC(#{since => <<"3.2.0">>}).
-type opts_no_profiles() :: #{
require_pkce => boolean(),
trusted_audiences => [binary()] | any,
preferred_auth_methods => [oidcc_auth_util:auth_method()],
request_opts => oidcc_http_util:request_opts()
}.
+
+?DOC(#{since => <<"3.2.0">>}).
-type error() :: {unknown_profile, atom()}.
-%% @private
+?DOC(false).
-spec apply_profiles(ClientContext, opts()) ->
{ok, ClientContext, opts_no_profiles()} | {error, error()}
when
diff --git a/src/oidcc_provider_configuration.erl b/src/oidcc_provider_configuration.erl
index 8a833df..b9b3533 100644
--- a/src/oidcc_provider_configuration.erl
+++ b/src/oidcc_provider_configuration.erl
@@ -1,24 +1,25 @@
-%%%-------------------------------------------------------------------
-%% @doc Tooling to load and parse Openid Configuration
-%%
-%% Records
-%%
-%% To use the record, import the definition:
-%%
-%% ```
-%% -include_lib(["oidcc/include/oidcc_provider_configuration.hrl"]).
-%% '''
-%%
-%% Telemetry
-%%
-%% See {@link 'Elixir.Oidcc.ProviderConfiguration'}
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_provider_configuration).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+Tooling to load and parse Openid Configuration.
+
+## Records
+
+To use the record, import the definition:
+
+```erlang
+-include_lib(["oidcc/include/oidcc_provider_configuration.hrl"]).
+```
+
+## Telemetry
+
+See [`Oidcc.ProviderConfiguration`](`m:'Elixir.Oidcc.ProviderConfiguration'`).
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_provider_configuration.hrl").
-export([decode_configuration/1]).
@@ -32,35 +33,48 @@
-export_type([quirks/0]).
-export_type([t/0]).
+?DOC("""
+Allow Specification Non-compliance.
+
+## Exceptions
+
+* `allow_unsafe_http` - Allow unsafe HTTP. Use this for development
+ providers and **never in production**.
+* `document_overrides` - a map to merge with the real OIDD document,
+ in case the OP left out some values.
+""").
+?DOC(#{since => <<"3.1.0">>}).
-type quirks() :: #{
allow_unsafe_http => boolean(),
document_overrides => map()
}.
-%% Allow Specification Non-compliance
-%%
-%% Exceptions
-%%
-%%
-%% - `allow_unsafe_http' - Allow unsafe HTTP. Use this for development
-%% providers and never in production.
-%% - `document_overrides' - a map to merge with the real OIDD document,
-%% in case the OP left out some values.
-%%
+?DOC("""
+Configure configuration loading / parsing.
+
+## Parameters
+
+* `fallback_expiry` - How long to keep configuration cached if the server doesn't specify expiry.
+* `request_opts` - config for HTTP request.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type opts() :: #{
fallback_expiry => timeout(),
request_opts => oidcc_http_util:request_opts(),
quirks => quirks()
}.
-%% Configure configuration loading / parsing
-%%
-%% Parameters
-%%
-%%
-%% - `fallback_expiry' - How long to keep configuration cached if the server doesn't specify expiry
-%% - `request_opts' - config for HTTP request
-%%
+?DOC("""
+Record containing OpenID and OAuth 2.0 Configuration.
+
+See:
+* https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
+* https://datatracker.ietf.org/doc/html/draft-jones-oauth-discovery-01#section-4.1
+* https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata
+
+All unrecognized fields are stored in `extra_fields`.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type t() ::
#oidcc_provider_configuration{
issuer :: uri_string:uri_string(),
@@ -127,14 +141,8 @@
mtls_endpoint_aliases :: #{binary() => uri_string:uri_string()},
extra_fields :: #{binary() => term()}
}.
-%% Record containing OpenID and OAuth 2.0 Configuration
-%%
-%% See [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata],
-%% [https://datatracker.ietf.org/doc/html/draft-jones-oauth-discovery-01#section-4.1] and
-%% [https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OPMetadata]
-%%
-%% All unrecognized fields are stored in `extra_fields'.
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
invalid_content_type
| {issuer_mismatch, Issuer :: binary()}
@@ -185,16 +193,17 @@
metadata => <<"#{jwks_uri => uri_string:uri_string()}">>
}).
-%% @doc Load OpenID Configuration into a {@link oidcc_provider_configuration:t()} record
-%%
-%% Examples
-%%
-%% ```
-%% {ok, #oidcc_provider_configuration{}} =
-%% oidcc_provider_configuration:load_configuration("https://accounts.google.com").
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Load OpenID Configuration into a `t:oidcc_provider_configuration:t/0` record.
+
+## Examples
+
+```erlang
+{ok, #oidcc_provider_configuration{}} =
+ oidcc_provider_configuration:load_configuration("https://accounts.google.com").
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec load_configuration(Issuer, Opts) ->
{ok, {Configuration :: t(), Expiry :: pos_integer()}} | {error, error()}
when
@@ -234,24 +243,25 @@ load_configuration(Issuer0, Opts) ->
{error, invalid_content_type}
end.
-%% @see load_configuration/2
-%% @since 3.1.0
+?DOC("See `load_configuration/2`.").
+?DOC(#{since => <<"3.1.0">>}).
-spec load_configuration(Issuer) ->
{ok, {Configuration :: t(), Expiry :: pos_integer()}} | {error, error()}
when
Issuer :: uri_string:uri_string().
load_configuration(Issuer) -> load_configuration(Issuer, #{}).
-%% @doc Load JWKs into a {@link jose_jwk:key()} record
-%%
-%% Examples
-%%
-%% ```
-%% {ok, #jose_jwk{}} =
-%% oidcc_provider_configuration:load_jwks("https://www.googleapis.com/oauth2/v3/certs").
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Load JWKs into a `t:jose_jwk:key/0` record.
+
+## Examples
+
+```erlang
+{ok, #jose_jwk{}} =
+ oidcc_provider_configuration:load_jwks("https://www.googleapis.com/oauth2/v3/certs").
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec load_jwks(JwksUri, Opts) ->
{ok, {Jwks :: jose_jwk:key(), Expiry :: pos_integer()}} | {error, term()}
when
@@ -274,21 +284,22 @@ load_jwks(JwksUri, Opts) ->
{ok, {{_Format, _Body}, _Headers}} -> {error, invalid_content_type}
end.
-%% @doc Decode JSON into a {@link oidcc_provider_configuration:t()} record
-%%
-%% Examples
-%%
-%% ```
-%% {ok, {{"HTTP/1.1",200,"OK"}, _Headers, Body}} =
-%% httpc:request("https://accounts.google.com/.well-known/openid-configuration"),
-%%
-%% {ok, DecodedJson} = your_json_lib:decode(Body),
-%%
-%% {ok, #oidcc_provider_configuration{}} =
-%% oidcc_provider_configuration:decode_configuration(DecodedJson).
-%% '''
-%% @end
-%% @since 3.1.0
+?DOC("""
+Decode JSON into a `t:oidcc_provider_configuration:t/0` record.
+
+## Examples
+
+```erlang
+{ok, {{"HTTP/1.1",200,"OK"}, _Headers, Body}} =
+ httpc:request("https://accounts.google.com/.well-known/openid-configuration"),
+
+{ok, DecodedJson} = your_json_lib:decode(Body),
+
+{ok, #oidcc_provider_configuration{}} =
+ oidcc_provider_configuration:decode_configuration(DecodedJson).
+```
+""").
+?DOC(#{since => <<"3.1.0">>}).
-spec decode_configuration(Configuration, Opts) -> {ok, t()} | {error, error()} when
Configuration :: map(), Opts :: opts().
decode_configuration(Configuration0, Opts) ->
@@ -567,8 +578,8 @@ decode_configuration(Configuration0, Opts) ->
}}
end.
-%% @see decode_configuration/2
-%% @since 3.0.0
+?DOC("See `decode_configuration/2`.").
+?DOC(#{since => <<"3.0.0">>}).
-spec decode_configuration(Configuration) -> {ok, t()} | {error, error()} when
Configuration :: map().
decode_configuration(Configuration) -> decode_configuration(Configuration, #{}).
diff --git a/src/oidcc_provider_configuration_worker.erl b/src/oidcc_provider_configuration_worker.erl
index 9e6d459..0c046ec 100644
--- a/src/oidcc_provider_configuration_worker.erl
+++ b/src/oidcc_provider_configuration_worker.erl
@@ -1,19 +1,20 @@
-%%%-------------------------------------------------------------------
-%% @doc OIDC Config Provider Worker
-%%
-%% Loads and continuously refreshes the OIDC configuration and JWKs
-%%
-%% The worker supports reading values concurrently via an ets table. To use
-%% this performance improvement, the worker has to be registered with a
-%% `{local, Name}'. No name / `{global, Name}' and `{via, RegModule, ViaName}'
-%% are not supported.
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_provider_configuration_worker).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+OIDC Config Provider Worker
+
+Loads and continuously refreshes the OIDC configuration and JWKs.
+
+The worker supports reading values concurrently via an ETS table. To use
+this performance improvement, the worker has to be registered with a
+`{local, Name}`. No name / `{global, Name}` and `{via, RegModule, ViaName}`
+are not supported.
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-behaviour(gen_server).
-include("oidcc_provider_configuration.hrl").
@@ -34,6 +35,20 @@
-export_type([opts/0]).
+?DOC("""
+Configuration Options
+
+* `name` - The gen_server name of the provider.
+* `issuer` - The issuer URI.
+* `provider_configuration_opts` - Options for the provider configuration
+ fetching.
+* `backoff_min` - The minimum backoff interval in ms (default: `1_000`).
+* `backoff_max` - The maximum backoff interval in ms (default: `30_000`).
+* `backoff_type` - The backoff strategy, `stop` for no backoff and to stop,
+ `exponential` for exponential, `random` for random, and `random_exponential`
+ for random exponential (default: `stop`).
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type opts() :: #{
name => gen_server:server_name(),
issuer := uri_string:uri_string(),
@@ -42,21 +57,6 @@
backoff_max => oidcc_backoff:max(),
backoff_type => oidcc_backoff:type()
}.
-%% Configuration Options
-%%
-%%
-%% - `name' - The gen_server name of the provider.
-%% - `issuer' - The issuer URI.
-%% - `provider_configuration_opts' - Options for the provider configuration
-%% fetching.
-%% - `backoff_min' - The minimum backoff interval in ms
-%% (default: `1_000`)
-%% - `backoff_max' - The maximum backoff interval in ms
-%% (default: `30_000`)
-%% - `backoff_type' - The backoff strategy, `stop' for no backoff and
-%% to stop, `exponential' for exponential, `random' for random and
-%% `random_exponential' for random exponential (default: `stop')
-%%
-record(state, {
provider_configuration = undefined :: #oidcc_provider_configuration{} | undefined,
@@ -74,40 +74,40 @@
-type state() :: #state{}.
-%% @doc Start Configuration Provider
-%%
-%% Examples
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://accounts.google.com">>,
-%% name => {local, google_config_provider}
-%% }).
-%% '''
-%%
-%% ```
-%% %% ...
-%%
-%% -behaviour(supervisor).
-%%
-%% %% ...
-%%
-%% init(_opts) ->
-%% SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
-%% ChildSpecs = [#{id => google_config_provider,
-%% start => {oidcc_provider_configuration_worker,
-%% start_link,
-%% [
-%% #{issuer => <<"https://accounts.google.com">>}
-%% ]},
-%% restart => permanent,
-%% type => worker,
-%% modules => [oidcc_provider_configuration_worker]}],
-%% {ok, {SupFlags, ChildSpecs}}.
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Start Configuration Provider.
+
+## Examples
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://accounts.google.com">>,
+ name => {local, google_config_provider}
+ }).
+```
+
+```erlang
+%% ...
+-behaviour(supervisor).
+
+%% ...
+
+init(_opts) ->
+ SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
+ ChildSpecs = [#{id => google_config_provider,
+ start => {oidcc_provider_configuration_worker,
+ start_link,
+ [
+ #{issuer => <<"https://accounts.google.com">>}
+ ]},
+ restart => permanent,
+ type => worker,
+ modules => [oidcc_provider_configuration_worker]}],
+ {ok, {SupFlags, ChildSpecs}}.
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec start_link(Opts :: opts()) -> gen_server:start_ret().
start_link(Opts) ->
case maps:get(name, Opts, undefined) of
@@ -117,7 +117,7 @@ start_link(Opts) ->
gen_server:start_link(Name, ?MODULE, Opts, [])
end.
-%% @private
+?DOC(false).
init(Opts) ->
EtsTable = register_ets_table(Opts),
maybe
@@ -135,7 +135,7 @@ init(Opts) ->
{continue, load_configuration}}
end.
-%% @private
+?DOC(false).
handle_call(
get_provider_configuration, _From, #state{provider_configuration = Configuration} = State
) ->
@@ -143,7 +143,7 @@ handle_call(
handle_call(get_jwks, _From, #state{jwks = Jwks} = State) ->
{reply, Jwks, State}.
-%% @private
+?DOC(false).
handle_cast(refresh_configuration, State) ->
{noreply, State, {continue, load_configuration}};
handle_cast(refresh_jwks, State) ->
@@ -163,7 +163,7 @@ handle_cast({refresh_jwks_for_unknown_kid, Kid}, #state{jwks = Jwks} = State) ->
{noreply, State}
end.
-%% @private
+?DOC(false).
handle_continue(
load_configuration,
#state{
@@ -226,7 +226,7 @@ handle_continue(
{error, Reason} -> handle_backoff_retry(jwks_load_failed, Reason, State)
end.
-%% @private
+?DOC(false).
handle_info(backoff_retry, State) ->
{noreply, State, {continue, load_configuration}};
handle_info(configuration_expired, State) ->
@@ -234,33 +234,34 @@ handle_info(configuration_expired, State) ->
handle_info(jwks_expired, State) ->
{noreply, State#state{jwks_refresh_timer = undefined}, {continue, load_jwks}}.
-%% @doc Get Configuration
+?DOC("Get Configuration.").
-spec get_provider_configuration(Name :: gen_server:server_ref()) ->
oidcc_provider_configuration:t() | undefined.
get_provider_configuration(Name) ->
lookup_in_ets_or_call(Name, provider_configuration, get_provider_configuration).
-%% @doc Get Parsed Jwks
+?DOC("Get Parsed Jwks.").
-spec get_jwks(Name :: gen_server:server_ref()) -> jose_jwk:key() | undefined.
get_jwks(Name) ->
lookup_in_ets_or_call(Name, jwks, get_jwks).
-%% @doc Refresh Configuration
-%%
-%% Examples
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://accounts.google.com">>
-%% }).
-%%
-%% %% Later
-%%
-%% oidcc_provider_configuration_worker:refresh_configuration(Pid).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Refresh Configuration.
+
+## Examples
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://accounts.google.com">>
+ }).
+
+%% Later
+
+oidcc_provider_configuration_worker:refresh_configuration(Pid).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec refresh_configuration(Name :: gen_server:server_ref()) -> ok.
refresh_configuration(Name) ->
refresh_configuration(Name, true).
@@ -273,22 +274,23 @@ refresh_configuration(Name, true) ->
gen_server:call(Name, get_provider_configuration),
ok.
-%% @doc Refresh JWKs
-%%
-%% Examples
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://accounts.google.com">>
-%% }).
-%%
-%% %% Later
-%%
-%% oidcc_provider_configuration_worker:refresh_jwks(Pid).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Refresh JWKs.
+
+## Examples
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://accounts.google.com">>
+ }).
+
+%% Later
+
+oidcc_provider_configuration_worker:refresh_jwks(Pid).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec refresh_jwks(Name :: gen_server:server_ref()) -> ok.
refresh_jwks(Name) -> refresh_jwks(Name, true).
@@ -300,20 +302,21 @@ refresh_jwks(Name, true) ->
gen_server:call(Name, get_jwks),
ok.
-%% @doc Refresh JWKs if the provided `Kid' is not matching any currently loaded keys
-%%
-%% Examples
-%%
-%% ```
-%% {ok, Pid} =
-%% oidcc_provider_configuration_worker:start_link(#{
-%% issuer => <<"https://accounts.google.com">>
-%% }).
-%%
-%% oidcc_provider_configuration_worker:refresh_jwks_for_unknown_kid(Pid, <<"kid">>).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Refresh JWKs if the provided `Kid` is not matching any currently loaded keys.
+
+## Examples
+
+```erlang
+{ok, Pid} =
+ oidcc_provider_configuration_worker:start_link(#{
+ issuer => <<"https://accounts.google.com">>
+ }).
+
+oidcc_provider_configuration_worker:refresh_jwks_for_unknown_kid(Pid, <<"kid">>).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec refresh_jwks_for_unknown_kid(Name :: gen_server:server_ref(), Kid :: binary()) ->
ok.
refresh_jwks_for_unknown_kid(Name, Kid) ->
@@ -339,8 +342,10 @@ get_issuer(Opts) ->
{ok, Issuer}
end.
-%% Checking of existing kid values is a bit wonky because of partial support
-%% in jose. see: https://github.com/potatosalad/erlang-jose/issues/28
+?DOC("""
+Checking of existing kid values is a bit wonky because of partial support
+in jose. See: https://github.com/potatosalad/erlang-jose/issues/28.
+""").
-spec has_kid(Jwk :: jose_jwk:key(), Kid :: binary()) -> boolean() | unknown.
has_kid(#jose_jwk{fields = #{<<"kid">> := Kid}}, Kid) ->
true;
diff --git a/src/oidcc_scope.erl b/src/oidcc_scope.erl
index d5e546d..749093c 100644
--- a/src/oidcc_scope.erl
+++ b/src/oidcc_scope.erl
@@ -1,12 +1,11 @@
-%%%-------------------------------------------------------------------
-%% @doc OpenID Scope Utilities
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_scope).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("OpenID Scope Utilities").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-export([parse/1]).
-export([query_append_scope/2]).
-export([scopes_to_bin/1]).
@@ -14,20 +13,23 @@
-export_type([scopes/0]).
-export_type([t/0]).
+?DOC(#{since => <<"3.0.0">>}).
-type scopes() :: [nonempty_binary() | atom() | nonempty_string()].
+?DOC(#{since => <<"3.0.0">>}).
-type t() :: binary().
-%% @doc Compose {@link scopes()} into {@link t()}
-%%
-%% Examples
-%%
-%% ```
-%% <<"openid profile email">> = oidcc_scope:scopes_to_bin(
-%% [<<"openid">>, profile, "email"]).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Compose `t:scopes/0` into `t:t/0`.
+
+## Examples
+
+```erlang
+<<"openid profile email">> = oidcc_scope:scopes_to_bin(
+ [<<"openid">>, profile, "email"]).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec scopes_to_bin(Scopes :: scopes()) -> t().
scopes_to_bin(Scopes) ->
NormalizedScopes =
@@ -45,7 +47,7 @@ scopes_to_bin(Scopes) ->
SeparatedScopes = lists:join(<<" ">>, NormalizedScopes),
list_to_binary(SeparatedScopes).
-%% @private
+?DOC(false).
-spec query_append_scope(Scope, QueryList) -> QueryList when
Scope :: t() | scopes(),
QueryList :: [{unicode:chardata(), unicode:chardata() | true}].
@@ -56,15 +58,16 @@ query_append_scope(Scope, QueryList) when is_binary(Scope) ->
query_append_scope(Scopes, QueryList) when is_list(Scopes) ->
query_append_scope(scopes_to_bin(Scopes), QueryList).
-%% @doc Parse {@link t()} into {@link scopes()}
-%%
-%% Examples
-%%
-%% ```
-%% [<<"openid">>, <<"profile">>] = oidcc_scope:parse(<<"openid profile">>).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Parse `t:t/0` into `t:scopes/0`.
+
+## Examples
+
+```erlang
+[<<"openid">>, <<"profile">>] = oidcc_scope:parse(<<"openid profile">>).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec parse(Scope :: t()) -> scopes().
parse(Scope) ->
binary:split(Scope, [<<" ">>], [trim_all, global]).
diff --git a/src/oidcc_token.erl b/src/oidcc_token.erl
index 2d4e566..3e9fcdc 100644
--- a/src/oidcc_token.erl
+++ b/src/oidcc_token.erl
@@ -1,24 +1,25 @@
-%%%-------------------------------------------------------------------
-%% @doc Facilitate OpenID Code/Token Exchanges
-%%
-%% Records
-%%
-%% To use the records, import the definition:
-%%
-%% ```
-%% -include_lib(["oidcc/include/oidcc_token.hrl"]).
-%% '''
-%%
-%% Telemetry
-%%
-%% See {@link 'Elixir.Oidcc.Token'}
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_token).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+Facilitate OpenID Code/Token Exchanges.
+
+## Records
+
+To use the records, import the definition:
+
+```erlang
+-include_lib(["oidcc/include/oidcc_token.hrl"]).
+```
+
+## Telemetry
+
+See [`Oidcc.Token`](`m:'Elixir.Oidcc.Token'`).
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
-include("oidcc_token.hrl").
@@ -52,37 +53,51 @@
-export_type([validate_jwt_opts/0]).
-export_type([t/0]).
+?DOC("""
+ID Token Wrapper.
+
+## Fields
+
+* `token` - The retrieved token.
+* `claims` - Unpacked claims of the verified token.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type id() :: #oidcc_token_id{token :: binary(), claims :: oidcc_jwt_util:claims()}.
-%% ID Token Wrapper
-%%
-%% Fields
-%%
-%%
-%% - `token' - The retrieved token
-%% - `claims' - Unpacked claims of the verified token
-%%
+?DOC("""
+Access Token Wrapper.
+
+## Fields
+
+* `token` - The retrieved token.
+* `expires` - Number of seconds the token is valid.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type access() ::
#oidcc_token_access{token :: binary(), expires :: pos_integer() | undefined, type :: binary()}.
-%% Access Token Wrapper
-%%
-%% Fields
-%%
-%%
-%% - `token' - The retrieved token
-%% - `expires' - Number of seconds the token is valid
-%%
+?DOC("""
+Refresh Token Wrapper.
+
+## Fields
+
+* `token` - The retrieved token.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type refresh() :: #oidcc_token_refresh{token :: binary()}.
-%% Refresh Token Wrapper
-%%
-%% Fields
-%%
-%%
-%% - `token' - The retrieved token
-%%
+?DOC("""
+Token Response Wrapper.
+
+## Fields
+
+* `id` - `t:id/0`.
+* `access` - `t:access/0`.
+* `refresh` - `t:refresh/0`.
+* `scope` - `t:oidcc_scope:scopes/0`.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type t() ::
#oidcc_token{
id :: oidcc_token:id() | none,
@@ -90,17 +105,28 @@
refresh :: oidcc_token:refresh() | none,
scope :: oidcc_scope:scopes()
}.
-%% Token Response Wrapper
-%%
-%% Fields
-%%
-%%
-%% - `id' - {@link id()}
-%% - `access' - {@link access()}
-%% - `refresh' - {@link refresh()}
-%% - `scope' - {@link oidcc_scope:scopes()}
-%%
+?DOC("""
+Options for retrieving a token.
+
+See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3.
+
+## Fields
+
+* `pkce_verifier` - PKCE verifier (random string previously given to
+ `m:oidcc_authorization`), see
+ https://datatracker.ietf.org/doc/html/rfc7636#section-4.1.
+* `require_pkce` - whether to require PKCE when getting the token.
+* `nonce` - Nonce to check.
+* `scope` - Scope to store with the token.
+* `refresh_jwks` - How to handle tokens with an unknown `kid`.
+ See `t:oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun/0`.
+* `redirect_uri` - Redirect URI given to `oidcc_authorization:create_redirect_url/2`.
+* `dpop_nonce` - if using DPoP, the `nonce` value to use in the proof claim.
+* `trusted_audiences` - if present, a list of additional audience values to
+ accept. Defaults to `any` which allows any additional values.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type retrieve_opts() ::
#{
pkce_verifier => binary(),
@@ -116,28 +142,9 @@
dpop_nonce => binary(),
trusted_audiences => [binary()] | any
}.
-%% Options for retrieving a token
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3]
-%%
-%% Fields
-%%
-%%
-%% - `pkce_verifier' - pkce verifier (random string previously given to
-%% `oidcc_authorization'), see
-%% [https://datatracker.ietf.org/doc/html/rfc7636#section-4.1]
-%% - `require_pkce' - whether to require PKCE when getting the token
-%% - `nonce' - Nonce to check
-%% - `scope' - Scope to store with the token
-%% - `refresh_jwks' - How to handle tokens with an unknown `kid'.
-%% See {@link oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun()}
-%% - `redirect_uri' - Redirect uri given to {@link oidcc_authorization:create_redirect_url/2}
-%% - `dpop_nonce' - if using DPoP, the `nonce' value to use in the
-%% proof claim
-%% - `trusted_audiences' - if present, a list of additional audience values to
-%% accept. Defaults to `any' which allows any additional values
-%%
+?DOC("See `t:refresh_opts_no_sub/0`.").
+?DOC(#{since => <<"3.0.0">>}).
-type refresh_opts_no_sub() ::
#{
scope => oidcc_scope:scopes(),
@@ -146,8 +153,9 @@
url_extension => oidcc_http_util:query_params(),
body_extension => oidcc_http_util:query_params()
}.
-%% See {@link refresh_opts_no_sub()}
+
+?DOC(#{since => <<"3.0.0">>}).
-type refresh_opts() ::
#{
scope => oidcc_scope:scopes(),
@@ -158,23 +166,26 @@
body_extension => oidcc_http_util:query_params()
}.
+?DOC("""
+Options for refreshing a token.
+
+See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3.
+
+## Fields
+
+* `scope` - Scope to store with the token.
+* `refresh_jwks` - How to handle tokens with an unknown `kid`.
+ See `t:oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun/0`.
+* `expected_subject` - `sub` of the original token.
+""").
+?DOC(#{since => <<"3.2.0">>}).
-type validate_jarm_opts() ::
#{
trusted_audiences => [binary()] | any
}.
-%% Options for refreshing a token
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3]
-%%
-%% Fields
-%%
-%%
-%% - `scope' - Scope to store with the token
-%% - `refresh_jwks' - How to handle tokens with an unknown `kid'.
-%% See {@link oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun()}
-%% - `expected_subject' - `sub' of the original token
-%%
+
+?DOC(#{since => <<"3.0.0">>}).
-type jwt_profile_opts() :: #{
scope => oidcc_scope:scopes(),
refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
@@ -184,6 +195,7 @@
body_extension => oidcc_http_util:query_params()
}.
+?DOC(#{since => <<"3.0.0">>}).
-type client_credentials_opts() :: #{
scope => oidcc_scope:scopes(),
refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
@@ -192,10 +204,12 @@
body_extension => oidcc_http_util:query_params()
}.
+?DOC(#{since => <<"3.0.0">>}).
-type authorization_headers_opts() :: #{
dpop_nonce => binary()
}.
+?DOC(#{since => <<"3.2.0">>}).
-type validate_jwt_opts() ::
#{
signing_algs => [binary()] | undefined,
@@ -203,6 +217,7 @@
encryption_encs => [binary()] | undefined
}.
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
{missing_claim, MissingClaim :: binary(), Claims :: oidcc_jwt_util:claims()}
| pkce_verifier_required
@@ -305,32 +320,32 @@
metadata => <<"#{issuer => uri_string:uri_string(), client_id => binary()}">>
}).
-%% @doc
-%% retrieve the token using the authcode received before and directly validate
-%% the result.
-%%
-%% the authcode was sent to the local endpoint by the OpenId Connect provider,
-%% using redirects
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:retrieve_token/5}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get AuthCode from Redirect
-%%
-%% {ok, #oidcc_token{}} =
-%% oidcc:retrieve(AuthCode, ClientContext, #{
-%% redirect_uri => <<"https://example.com/callback">>}).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Retrieve the token using the authcode received before and directly validate
+the result.
+
+The authcode was sent to the local endpoint by the OpenId Connect provider,
+using redirects.
+
+For a high level interface using `m:oidcc_provider_configuration_worker`
+see `oidcc:retrieve_token/5`.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+%% Get AuthCode from Redirect
+
+{ok, #oidcc_token{}} =
+ oidcc:retrieve(AuthCode, ClientContext, #{
+ redirect_uri => <<"https://example.com/callback">>}).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec retrieve(AuthCode, ClientContext, Opts) ->
{ok, t()} | {error, error()}
when
@@ -371,30 +386,30 @@ retrieve(AuthCode, ClientContext, Opts) ->
{error, {grant_type_not_supported, authorization_code}}
end.
-%% @doc
-%% Validate the JARM response, returning the valid claims as a map.
-%%
-%% The response was sent to the local endpoint by the OpenId Connect provider,
-%% using redirects
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get Response from Redirect
-%%
-%% {ok, #{<<"code">> := AuthCode}} =
-%% oidcc:validate_jarm(Response, ClientContext, #{}),
-%%
-%% {ok, #oidcc_token{}} = oidcc:retrieve(AuthCode, ClientContext,
-%% #{redirect_uri => <<"https://redirect.example/">>}.
-%% '''
-%% @end
-%% @since 3.2.0
+?DOC("""
+Validate the JARM response, returning the valid claims as a map.
+
+The response was sent to the local endpoint by the OpenId Connect provider,
+using redirects.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+%% Get Response from Redirect
+
+{ok, #{<<"code">> := AuthCode}} =
+ oidcc:validate_jarm(Response, ClientContext, #{}),
+
+{ok, #oidcc_token{}} = oidcc:retrieve(AuthCode, ClientContext,
+ #{redirect_uri => <<"https://redirect.example/">>}).
+```
+""").
+?DOC(#{since => <<"3.2.0">>}).
-spec validate_jarm(Response, ClientContext, Opts) ->
{ok, oidcc_jwt_util:claims()} | {error, error()}
when
@@ -449,34 +464,35 @@ validate_jarm(Response, ClientContext, Opts) ->
{ok, Claims}
end.
-%% @doc Refresh Token
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:refresh_token/5}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get AuthCode from Redirect
-%%
-%% {ok, Token} =
-%% oidcc_token:retrieve(AuthCode, ClientContext, #{
-%% redirect_uri => <<"https://example.com/callback">>}).
-%%
-%% %% Later
-%%
-%% {ok, #oidcc_token{}} =
-%% oidcc_token:refresh(Token,
-%% ClientContext,
-%% #{expected_subject => <<"sub_from_initial_id_token>>}).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Refresh Token
+
+For a high level interface using `m:oidcc_provider_configuration_worker`
+see `oidcc:refresh_token/5`.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+%% Get AuthCode from Redirect
+
+{ok, Token} =
+ oidcc_token:retrieve(AuthCode, ClientContext, #{
+ redirect_uri => <<"https://example.com/callback">>}).
+
+%% Later
+
+{ok, #oidcc_token{}} =
+ oidcc_token:refresh(Token,
+ ClientContext,
+ #{expected_subject => <<"sub_from_initial_id_token">>}).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec refresh
(RefreshToken, ClientContext, Opts) ->
{ok, t()} | {error, error()}
@@ -536,34 +552,35 @@ refresh(RefreshToken, ClientContext, Opts) ->
{error, {grant_type_not_supported, refresh_token}}
end.
-%% @doc Retrieve JSON Web Token (JWT) Profile Token
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc7523#section-4]
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:jwt_profile_token/6}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% {ok, KeyJson} = file:read_file("jwt-profile.json"),
-%% KeyMap = jose:decode(KeyJson),
-%% Key = jose_jwk:from_pem(maps:get(<<"key">>, KeyMap)),
-%%
-%% {ok, #oidcc_token{}} =
-%% oidcc_token:jwt_profile(<<"subject">>,
-%% ClientContext,
-%% Key,
-%% #{scope => [<<"scope">>],
-%% kid => maps:get(<<"keyId">>, KeyMap)}).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Retrieve JSON Web Token (JWT) Profile Token
+
+See [https://datatracker.ietf.org/doc/html/rfc7523#section-4]
+
+For a high level interface using {@link oidcc_provider_configuration_worker}
+see {@link oidcc:jwt_profile_token/6}.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+{ok, KeyJson} = file:read_file("jwt-profile.json"),
+KeyMap = jose:decode(KeyJson),
+Key = jose_jwk:from_pem(maps:get(<<"key">>, KeyMap)),
+
+{ok, #oidcc_token{}} =
+ oidcc_token:jwt_profile(<<"subject">>,
+ ClientContext,
+ Key,
+ #{scope => [<<"scope">>],
+ kid => maps:get(<<"keyId">>, KeyMap)}).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec jwt_profile(Subject, ClientContext, Jwk, Opts) -> {ok, t()} | {error, error()} when
Subject :: binary(),
ClientContext :: oidcc_client_context:t(),
@@ -935,27 +952,26 @@ validate_id_token(IdToken, ClientContext, Opts) when is_map(Opts) ->
end
end.
-%% @doc Validate JWT
-%%
-%% Validates a generic JWT (such as an access token) from the given provider.
-%% Useful if the issuer is shared between multiple applications, and the access token
-%% generated for a user at one client is used to validate their access at another client.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get Jwt from Authorization header
-%%
-%% {ok, Claims} =
-%% oidcc:validate_jwt(Jwt, ClientContext, Opts).
-%% '''
-%% @end
-%% @since 3.2.0
+?DOC("""
+Validate JWT
+
+Validates a generic JWT (such as an access token) from the given provider.
+Useful if the issuer is shared between multiple applications, and the access token
+generated for a user at one client is used to validate their access at another client.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+%% Get Jwt from Authorization header
+{ok, Claims} =
+ oidcc:validate_jwt(Jwt, ClientContext, Opts).
+```
+""").
+?DOC(#{since => <<"3.2.0">>}).
-spec validate_jwt(Jwt, ClientContext, Opts) ->
{ok, Claims} | {error, error()}
when
@@ -1003,26 +1019,25 @@ validate_jwt(Jwt, ClientContext, Opts) when is_map(Opts) ->
{ok, Claims}
end.
-%% @doc Authorization headers
-%%
-%% Generate a map of authorization headers to use when using the given
-%% access token to access an API endpoint.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get Access Token record from somewhere
-%%
-%% Headers =
-%% oidcc:authorization_headers(AccessTokenRecord, :get, Url, ClientContext).
-%% '''
-%% @end
-%% @since 3.2.0
+?DOC("""
+Authorization headers
+
+Generate a map of authorization headers to use when using the given
+access token to access an API endpoint.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+%% Get Access Token record from somewhere
+Headers =
+ oidcc:authorization_headers(AccessTokenRecord, :get, Url, ClientContext).
+```
+""").
+?DOC(#{since => "3.2.0"}).
-spec authorization_headers(AccessTokenRecord, Method, Endpoint, ClientContext) -> HeaderMap when
AccessTokenRecord :: access(),
Method :: post | get,
diff --git a/src/oidcc_token_introspection.erl b/src/oidcc_token_introspection.erl
index ac0a1ab..e259d7a 100644
--- a/src/oidcc_token_introspection.erl
+++ b/src/oidcc_token_introspection.erl
@@ -1,26 +1,27 @@
-%%%-------------------------------------------------------------------
-%% @doc OAuth Token Introspection
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc7662]
-%%
-%% Records
-%%
-%% To use the records, import the definition:
-%%
-%% ```
-%% -include_lib(["oidcc/include/oidcc_token_introspection.hrl"]).
-%% '''
-%%
-%% Telemetry
-%%
-%% See {@link 'Elixir.Oidcc.TokenIntrospection'}
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_token_introspection).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+OAuth Token Introspection.
+
+See https://datatracker.ietf.org/doc/html/rfc7662.
+
+## Records
+
+To use the records, import the definition:
+
+```erlang
+-include_lib(["oidcc/include/oidcc_token_introspection.hrl"]).
+```
+
+## Telemetry
+
+See [`Oidcc.TokenIntrospection`](`m:'Elixir.Oidcc.TokenIntrospection'`).
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
-include("oidcc_token.hrl").
@@ -32,6 +33,12 @@
-export_type([opts/0]).
-export_type([t/0]).
+?DOC("""
+Introspection Result.
+
+See https://datatracker.ietf.org/doc/html/rfc7662#section-2.2.
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type t() :: #oidcc_token_introspection{
active :: boolean(),
client_id :: binary(),
@@ -39,16 +46,16 @@
scope :: oidcc_scope:scopes(),
username :: binary()
}.
-%% Introspection Result
-%%
-%% See [https://datatracker.ietf.org/doc/html/rfc7662#section-2.2]
+
+?DOC(#{since => <<"3.0.0">>}).
-type opts() :: #{
preferred_auth_methods => [oidcc_auth_util:auth_method(), ...],
request_opts => oidcc_http_util:request_opts(),
dpop_nonce => binary()
}.
+?DOC(#{since => <<"3.0.0">>}).
-type error() :: client_id_mismatch | introspection_not_supported | oidcc_http_util:error().
-telemetry_event(#{
@@ -72,27 +79,27 @@
metadata => <<"#{issuer => uri_string:uri_string(), client_id => binary()}">>
}).
-%% @doc
-%% Introspect the given access token
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:introspect_token/5}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get AccessToken
-%%
-%% {ok, #oidcc_token_introspection{active = True}} =
-%% oidcc_token_introspection:introspect(AccessToken, ClientContext, #{}).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Introspect the given access token.
+
+For a high level interface using `m:oidcc_provider_configuration_worker`
+see `oidcc:introspect_token/5`.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+%% Get AccessToken
+
+{ok, #oidcc_token_introspection{active = True}} =
+ oidcc_token_introspection:introspect(AccessToken, ClientContext, #{}).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec introspect(Token, ClientContext, Opts) ->
{ok, t()}
| {error, error()}
diff --git a/src/oidcc_userinfo.erl b/src/oidcc_userinfo.erl
index 96fb036..157ea85 100644
--- a/src/oidcc_userinfo.erl
+++ b/src/oidcc_userinfo.erl
@@ -1,18 +1,19 @@
-%%%-------------------------------------------------------------------
-%% @doc OpenID Connect Userinfo
-%%
-%% See [https://openid.net/specs/openid-connect-core-1_0.html#UserInfo]
-%%
-%% Telemetry
-%%
-%% See {@link 'Elixir.Oidcc.Userinfo'}
-%% @end
-%% @since 3.0.0
-%%%-------------------------------------------------------------------
-module(oidcc_userinfo).
-feature(maybe_expr, enable).
+-include("internal/doc.hrl").
+?MODULEDOC("""
+OpenID Connect Userinfo
+
+See https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
+
+## Telemetry
+
+See [`Oidcc.Userinfo`](`m:'Elixir.Oidcc.Userinfo'`).
+""").
+?MODULEDOC(#{since => <<"3.0.0">>}).
+
-include("oidcc_client_context.hrl").
-include("oidcc_provider_configuration.hrl").
-include("oidcc_token.hrl").
@@ -28,34 +29,37 @@
-export_type([retrieve_opts/0]).
-export_type([retrieve_opts_no_sub/0]).
+?DOC("See `t:retrieve_opts/0`.").
+?DOC(#{since => <<"3.0.0">>}).
-type retrieve_opts_no_sub() ::
#{
refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
dpop_nonce => binary()
}.
-%% See {@link retrieve_opts()}
+?DOC("""
+Configure userinfo request
+
+See https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest
+
+## Parameters
+
+* `refresh_jwks` - How to handle tokens with an unknown `kid`.
+ See `t:oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun/0`
+* `expected_subject` - expected subject for the userinfo
+ (`sub` from id token)
+* `dpop_nonce` - if using DPoP, the `nonce` value to use in the
+ proof claim
+""").
+?DOC(#{since => <<"3.0.0">>}).
-type retrieve_opts() ::
#{
refresh_jwks => oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun(),
expected_subject => binary() | any,
dpop_nonce => binary()
}.
-%% Configure userinfo request
-%%
-%% See [https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest]
-%%
-%% Parameters
-%%
-%%
-%% - `refresh_jwks' - How to handle tokens with an unknown `kid'.
-%% See {@link oidcc_jwt_util:refresh_jwks_for_unknown_kid_fun()}
-%% - `expected_subject' - expected subject for the userinfo
-%% (`sub' from id token)
-%% - `dpop_nonce' - if using DPoP, the `nonce' value to use in the
-%% proof claim
-%%
+?DOC(#{since => <<"3.0.0">>}).
-type error() ::
{distributed_claim_not_found, {ClaimSource :: binary(), ClaimName :: binary()}}
| no_access_token
@@ -85,27 +89,27 @@
metadata => <<"#{issuer => uri_string:uri_string(), client_id => binary()}">>
}).
-%% @doc
-%% Load userinfo for the given token
-%%
-%% For a high level interface using {@link oidcc_provider_configuration_worker}
-%% see {@link oidcc:retrieve_userinfo/5}.
-%%
-%% Examples
-%%
-%% ```
-%% {ok, ClientContext} =
-%% oidcc_client_context:from_configuration_worker(provider_name,
-%% <<"client_id">>,
-%% <<"client_secret">>),
-%%
-%% %% Get Token
-%%
-%% {ok, #{<<"sub">> => Sub}} =
-%% oidcc_userinfo:retrieve(Token, ClientContext, #{}).
-%% '''
-%% @end
-%% @since 3.0.0
+?DOC("""
+Load userinfo for the given token
+
+For a high level interface using `m:oidcc_provider_configuration_worker`, see
+`oidcc:retrieve_userinfo/5`.
+
+## Examples
+
+```erlang
+{ok, ClientContext} =
+ oidcc_client_context:from_configuration_worker(provider_name,
+ <<"client_id">>,
+ <<"client_secret">>),
+
+%% Get Token
+
+{ok, #{<<"sub">> => Sub}} =
+ oidcc_userinfo:retrieve(Token, ClientContext, #{}).
+```
+""").
+?DOC(#{since => <<"3.0.0">>}).
-spec retrieve
(Token, ClientContext, Opts) -> {ok, oidcc_jwt_util:claims()} | {error, error()} when
Token :: oidcc_token:t(),