Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cf4b2f6
store presentation previous code
patatoid Jun 7, 2025
aec7533
do not check public client id in siopv2
patatoid Jun 7, 2025
f6798a8
code chains validation in direct post requests
patatoid Jun 8, 2025
38effab
avoid code chains replay attacks
patatoid Jun 8, 2025
c652655
WIP check public client id in chain
patatoid Jun 8, 2025
65ba299
issuance and presentation siopv2 hybrid grants
patatoid Jun 11, 2025
db436c3
issuance code chains management
patatoid Jun 14, 2025
7a67c45
validate code in presentation chains
patatoid Jun 14, 2025
353fbf6
oid4vci public client id check error wording
patatoid Jun 14, 2025
f237ee5
validate preauthorized code authorization previous code
patatoid Jun 17, 2025
1ac3f48
return preauthorized code errors as query
patatoid Jun 17, 2025
21648d7
[ssi] refactor check public client id
patatoid Jun 21, 2025
9d57c05
WIP provider policies registration
patatoid Jun 22, 2025
96f1b3b
WIP code bound metadata policy
patatoid Jun 22, 2025
037046e
Revert "WIP code bound metadata policy"
patatoid Jun 22, 2025
5115890
Revert "Revert "WIP code bound metadata policy""
patatoid Jun 22, 2025
8b1d911
continue in case of siop metadata policy error
patatoid Jun 22, 2025
88e66e5
refactoring + apply metadata policies at issuance
patatoid Jun 23, 2025
8f720f1
anonymous user scope authorization
patatoid Jun 24, 2025
188f1d1
add code to credential offer responses
patatoid Jun 24, 2025
9aa2004
add client_id to credential offer responses
patatoid Aug 23, 2025
35199fd
credential offer responses redirect to deeplink
patatoid Aug 23, 2025
cbd4199
revoke code chain at credential issuance
patatoid Aug 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
# TODO: enable by default in Credo 1.1
{Credo.Check.Readability.UnnecessaryAliasExpansion, false},
{Credo.Check.Readability.VariableNames, []},
{Credo.Check.Readability.WithSingleClause, false},

#
## Refactoring Opportunities
Expand Down
2 changes: 2 additions & 0 deletions lib/boruta/adapters/codes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ defmodule Boruta.CodesAdapter do
def create(params), do: codes().create(params)
def revoke(code), do: codes().revoke(code)
def revoke_previous_token(code), do: codes().revoke_previous_token(code)
def update_sub(code, sub, metadata_policy), do: codes().update_sub(code, sub, metadata_policy)
def code_chain(code), do: codes().code_chain(code)
end
87 changes: 82 additions & 5 deletions lib/boruta/adapters/ecto/codes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ defmodule Boruta.Ecto.Codes do

def get_by(id: id) do
with {:ok, id} <- Ecto.UUID.cast(id),
{:ok, token} <- TokenStore.get(id: id) do
token
{:ok, token} <- TokenStore.get(id: id) do
token
else
:error -> nil
:error ->
nil

{:error, "Not cached."} ->
with %Token{} = token <-
repo().one(
Expand All @@ -64,7 +66,8 @@ defmodule Boruta.Ecto.Codes do
token

{:error, "Not cached."} ->
with %Token{} = token <-
with "" <> value <- value,
%Token{} = token <-
repo().one(
from t in Token,
where: t.type in ["code", "preauthorized_code"] and t.value == ^value
Expand All @@ -74,6 +77,9 @@ defmodule Boruta.Ecto.Codes do
|> to_oauth_schema()
|> TokenStore.put() do
token
else
{:error, error} -> {:error, error}
nil -> {:error, "Code not found."}
end
end
end
Expand All @@ -100,6 +106,7 @@ defmodule Boruta.Ecto.Codes do
apply(Token, changeset_method(client), [
%Token{resource_owner: params[:resource_owner]},
%{
response_type: params[:response_type],
client_id: client_id,
sub: sub,
redirect_uri: redirect_uri,
Expand All @@ -111,7 +118,8 @@ defmodule Boruta.Ecto.Codes do
code_challenge_method: code_challenge_method,
authorization_details: authorization_details,
presentation_definition: params[:presentation_definition],
public_client_id: params[:public_client_id]
public_client_id: params[:public_client_id],
previous_code: params[:previous_code]
}
])

Expand All @@ -130,6 +138,30 @@ defmodule Boruta.Ecto.Codes do
defp changeset_method(%Oauth.Client{pkce: true}), do: :pkce_code_changeset

@impl Boruta.Oauth.Codes
def revoke(codes) when is_list(codes) do
code_count = Enum.count(codes)
code_ids = Enum.map(codes, fn code -> code.id end)
now = DateTime.utc_now()

with {^code_count, _} <-
from(t in Token, where: t.id in ^code_ids)
|> repo().update_all(set: [revoked_at: now]),
:ok <-
Enum.reduce(codes, :ok, fn code, acc ->
case TokenStore.invalidate(code) do
{:ok, _token} ->
acc

error ->
error
end
end) do
{:ok, Enum.map(codes, fn code -> %{code | revoked_at: now} end)}
else
_ -> {:error, "Could not revoke code chain."}
end
end

def revoke(%Oauth.Token{value: value} = code) do
with %Token{} = token <- repo().get_by(Token, value: value),
{:ok, token} <-
Expand All @@ -155,4 +187,49 @@ defmodule Boruta.Ecto.Codes do
{:ok, code}
end
end

@impl Boruta.Oauth.Codes
def update_sub(%Oauth.Token{id: id}, sub, metadata_policy) do
with %Token{} = code <-
repo().one(
from t in Token,
where: t.type in ["code", "preauthorized_code"] and t.id == ^id
),
{:ok, code} <- Token.sub_changeset(code, sub, metadata_policy) |> repo().update(),
{:ok, code} <- TokenStore.invalidate(code) do
{:ok, to_oauth_schema(code)}
else
_ ->
{:error, "Preauthorized code not found."}
end
end

@impl Boruta.Oauth.Codes
def code_chain(token, acc \\ [])

def code_chain(%Oauth.Token{previous_code: nil} = code, acc) do
Enum.reject([code | acc], &is_nil/1) |> Enum.reverse()
end

def code_chain(%Oauth.Token{type: "preauthorized_code", previous_code: value} = code, acc) do
case code_chain(get_by(value: value)) do
chain when is_list(chain) ->
[code | acc ++ chain]

_ ->
acc
end
end

def code_chain(%Oauth.Token{type: "code", previous_code: value} = code, acc) do
case code_chain(get_by(value: value)) do
chain when is_list(chain) ->
[code | acc ++ chain]

_ ->
acc
end
end

def code_chain(nil, _acc), do: {:error, "Previous code not found."}
end
21 changes: 13 additions & 8 deletions lib/boruta/adapters/ecto/preauthorized_codes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ defmodule Boruta.Ecto.PreauthorizedCodes do
id: client_id,
authorization_code_ttl: authorization_code_ttl
} = client,
resource_owner: resource_owner,
scope: scope,
state: state,
redirect_uri: redirect_uri
Expand All @@ -29,17 +28,23 @@ defmodule Boruta.Ecto.PreauthorizedCodes do
# TODO store resource owner credentials
changeset =
apply(Token, changeset_method(client), [
%Token{resource_owner: resource_owner},
%Token{resource_owner: params[:resource_owner]},
%{
agent_token: params[:agent_token],
authorization_code_ttl: authorization_code_ttl,
authorization_details: params[:authorization_details],
client_id: client_id,
sub: sub,
state: state,
code_challenge: params[:code_challenge],
code_challenge_method: params[:code_challenge_method],
nonce: params[:nonce],
agent_token: params[:agent_token],
scope: scope,
presentation_definition: params[:presentation_definition],
previous_code: params[:previous_code],
public_client_id: params[:public_client_id],
redirect_uri: redirect_uri,
authorization_code_ttl: authorization_code_ttl,
authorization_details: resource_owner.authorization_details
response_type: params[:response_type],
scope: scope,
state: state,
sub: sub
}
])

Expand Down
49 changes: 34 additions & 15 deletions lib/boruta/adapters/ecto/schemas/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule Boruta.Ecto.Token do
@type t :: %__MODULE__{
type: String.t(),
value: String.t(),
response_type: String.t() | nil,
tx_code: String.t() | nil,
authorization_details: list(),
state: String.t(),
Expand Down Expand Up @@ -60,6 +61,7 @@ defmodule Boruta.Ecto.Token do
schema "oauth_tokens" do
field(:type, :string)
field(:value, :string)
field(:response_type, :string)
field(:authorization_details, {:array, :map}, default: [])
field(:presentation_definition, :map)
field(:refresh_token, :string)
Expand All @@ -83,6 +85,7 @@ defmodule Boruta.Ecto.Token do
field(:agent_token, :string)
field(:bind_data, :map)
field(:bind_configuration, :map)
field(:metadata_policy, :map)

field(:resource_owner, :map, virtual: true)

Expand Down Expand Up @@ -198,17 +201,21 @@ defmodule Boruta.Ecto.Token do
def preauthorized_code_changeset(token, attrs) do
token
|> cast(attrs, [
:response_type,
:agent_token,
:authorization_code_ttl,
:authorization_details,
:client_id,
:sub,
:state,
:nonce,
:scope,
:authorization_details,
:presentation_definition,
:previous_code,
:public_client_id,
:redirect_uri,
:agent_token
:scope,
:state,
:sub
])
|> validate_required([:authorization_code_ttl, :client_id, :sub])
|> validate_required([:authorization_code_ttl, :client_id])
|> foreign_key_constraint(:client_id)
|> put_change(:type, "preauthorized_code")
|> put_value()
Expand All @@ -220,22 +227,25 @@ defmodule Boruta.Ecto.Token do
def pkce_preauthorized_code_changeset(token, attrs) do
token
|> cast(attrs, [
:response_type,
:agent_token,
:authorization_code_ttl,
:authorization_details,
:client_id,
:sub,
:state,
:nonce,
:scope,
:code_challenge,
:code_challenge_method,
:authorization_details,
:nonce,
:presentation_definition,
:previous_code,
:public_client_id,
:redirect_uri,
:agent_token
:scope,
:state,
:sub
])
|> validate_required([
:authorization_code_ttl,
:client_id,
:sub,
:code_challenge
])
|> foreign_key_constraint(:client_id)
Expand All @@ -251,6 +261,7 @@ defmodule Boruta.Ecto.Token do
def code_changeset(token, attrs) do
token
|> cast(attrs, [
:response_type,
:authorization_code_ttl,
:client_id,
:public_client_id,
Expand All @@ -260,7 +271,8 @@ defmodule Boruta.Ecto.Token do
:nonce,
:scope,
:authorization_details,
:presentation_definition
:presentation_definition,
:previous_code
])
|> validate_required([:authorization_code_ttl, :client_id, :sub, :redirect_uri])
|> foreign_key_constraint(:client_id)
Expand All @@ -273,6 +285,7 @@ defmodule Boruta.Ecto.Token do
def pkce_code_changeset(token, attrs) do
token
|> cast(attrs, [
:response_type,
:authorization_code_ttl,
:client_id,
:public_client_id,
Expand All @@ -284,7 +297,8 @@ defmodule Boruta.Ecto.Token do
:code_challenge,
:code_challenge_method,
:authorization_details,
:presentation_definition
:presentation_definition,
:previous_code
])
|> validate_required([
:authorization_code_ttl,
Expand All @@ -302,6 +316,11 @@ defmodule Boruta.Ecto.Token do
|> encrypt_code_challenge()
end

@doc false
def sub_changeset(code, sub, metadata_policy) do
change(code, %{sub: sub, type: "code", metadata_policy: metadata_policy})
end

@doc false
def revoke_refresh_token_changeset(token) do
now = DateTime.utc_now()
Expand Down
2 changes: 1 addition & 1 deletion lib/boruta/adapters/ecto/stores/token_store.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ defmodule Boruta.Ecto.TokenStore do
end

@spec invalidate(token :: Boruta.Oauth.Token.t()) ::
{:ok, token :: Boruta.Oauth.Token.t()}
{:ok, token :: Boruta.Oauth.Token.t()} | {:error, term()}
def invalidate(token) do
with :ok <- cache_backend().delete({Token, :value, token.value}),
:ok <- cache_backend().delete({Token, :refresh_token, token.refresh_token}) do
Expand Down
Loading
Loading