diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index 093b120..b3ac6dd 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -5,21 +5,27 @@ on: [push]
jobs:
build_and_test:
runs-on: ubuntu-latest
- name: OTP ${{matrix.otp}}
strategy:
matrix:
- otp: ["23.2", "24.0"]
+ otp: ["24.0", "25.0"]
+ name: OTP ${{matrix.otp}}
steps:
- - uses: actions/checkout@v2.0.0
- - uses: gleam-lang/setup-erlang@v1.1.2
+ - uses: actions/checkout@v4
+
+ - uses: erlef/setup-beam@v1.16.0
with:
- otp-version: ${{matrix.otp}}
+ otp-version: ${{ matrix.otp }}
+ rebar3-version: "3.20.0"
+
- name: Compile
run: make compile
+
- name: Run xref
run: make xref
+
- name: Run dialyzer
run: make dialyze
+
- name: Run eunit tests
run: make eunit
@@ -28,16 +34,7 @@ jobs:
needs: build_and_test
runs-on: ubuntu-latest
steps:
- - name: Bump version and push tag
- id: tag_version
- uses: mathieudutour/github-tag-action@v5.3
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- - name: Create a GitHub release
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: ${{ steps.tag_version.outputs.new_tag }}
- release_name: Release ${{ steps.tag_version.outputs.new_tag }}
- body: ${{ steps.tag_version.outputs.changelog }}
+ - name: Create Tag (Release)
+ uses: kivra/github-tag-action@v6.1
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 3a1fc46..2ce923e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ ebin
deps
.rebar
*.plt
-_build/*
\ No newline at end of file
+_build/*
+*.xml
diff --git a/TEST-file_oauth2.app.xml b/TEST-file_oauth2.app.xml
deleted file mode 100644
index 0bc26db..0000000
--- a/TEST-file_oauth2.app.xml
+++ /dev/null
@@ -1,391 +0,0 @@
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2_tests:'-bad_authorize_password_test_/0-fun-1-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 53)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505)
-in call from eunit_proc:tests_inorder/3 (eunit_proc.erl, line 447)
-**error:undef
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_password/4 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 114)
-in call from oauth2_tests:'-bad_authorize_password_test_/0-fun-9-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 73)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505)
-**error:undef
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_password/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 134)
-in call from oauth2_tests:'-authorize_implicit_grant_test_/0-fun-1-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 113)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505)
-**error:undef
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197)
-in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426)
-in call from oauth2_tests:'-bad_ttl_test_/0-fun-3-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 178)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-**error:undef
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197)
-in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426)
-in call from oauth2_tests:'-bad_ttl_test_/0-fun-5-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 187)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-**error:undef
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197)
-in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426)
-in call from oauth2_tests:'-verify_access_code_test_/0-fun-3-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 269)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-**error:undef
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197)
-in call from oauth2_tests:'-bad_refresh_token_test_/0-fun-4-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 299)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-in call from eunit_proc:handle_test/2 (eunit_proc.erl, line 505)
-**error:undef
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_code_request/5 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 197)
-in call from oauth2_tests:issue_access_code/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 426)
-in call from oauth2_tests:'-verify_refresh_token_test_/0-fun-1-'/0 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 352)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-in call from eunit_proc:with_timeout/3 (eunit_proc.erl, line 347)
-**error:undef
-
-
-
-
-
-
-::in function oauth2_mock_backend:jwt_issuer/0
- called as jwt_issuer()
-in call from oauth2:auth_user/3 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 450)
-in call from oauth2:authorize_password/4 (/Users/moritz/Projects/kivra/oauth2/src/oauth2.erl, line 114)
-in call from oauth2_tests:issue_token_and_refresh_with_user_name_and_password/2 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 448)
-in call from oauth2_tests:'-verify_refresh_token_test_/0-fun-4-'/1 (/Users/moritz/Projects/kivra/oauth2/test/oauth2_tests.erl, line 368)
-in call from lists:foreach/2 (lists.erl, line 1342)
-in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
-in call from eunit_proc:run_test/1 (eunit_proc.erl, line 522)
-**error:undef
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/TEST-oauth2_mock_backend.xml b/TEST-oauth2_mock_backend.xml
deleted file mode 100644
index 06f0e24..0000000
--- a/TEST-oauth2_mock_backend.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/rebar.config b/rebar.config
index 228674b..7bdbbc5 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
{xref_checks, [undefined_function_calls]}.
{eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}.
-{profiles, [ {test, [{deps, [ {meck, {git, "git@github.com:kivra/meck.git", {tag, "0.8.13"}}},
+{profiles, [ {test, [{deps, [ {meck, "0.9.2"},
{proper, {git, "https://github.com/manopapad/proper.git", {tag, "v1.4"}}}
]}]}
]}.
diff --git a/src/oauth2.erl b/src/oauth2.erl
index 65bb723..354fa9e 100644
--- a/src/oauth2.erl
+++ b/src/oauth2.erl
@@ -35,15 +35,11 @@
-export([authorize_directly/4]).
-export([issue_code/2]).
-export([issue_token/2]).
--export([issue_jwt/2]).
-export([issue_token_and_refresh/2]).
--export([issue_jwt_and_refresh/3]).
-export([verify_access_token/2]).
-export([verify_access_code/2]).
-export([verify_access_code/3]).
--export([verify_jwt/1]).
-export([refresh_access_token/4]).
--export([refresh_jwt/4]).
-export_type([token/0]).
-export_type([user/0]).
@@ -63,28 +59,29 @@
%%%_ * Types -----------------------------------------------------------
%% Opaque authentication record
-record(a, { client = undefined :: undefined | client()
+ , device_id = undefined :: undefined | device_id()
, resowner = undefined :: undefined | resowner()
, scope :: scope()
, ttl = 0 :: non_neg_integer()
- , issuer = undefined :: undefined | binary()
}).
--type context() :: proplists:proplist().
--type auth() :: #a{}.
--type user() :: any(). %% Opaque User Object
--type client() :: any(). %% Opaque Client Object
--type resowner() :: any(). %% Opaque Resource Owner Object
--type rediruri() :: any(). %% Opaque Redirection URI
--type token() :: binary().
--type response() :: oauth2_response:response().
--type lifetime() :: non_neg_integer().
--type scope() :: list(binary()) | binary().
--type appctx() :: term().
--type error() :: access_denied | invalid_client | invalid_grant |
- invalid_request | invalid_authorization | invalid_scope |
- unauthorized_client | unsupported_grant_type |
- unsupported_response_type | server_error |
- temporarily_unavailable | atom().
+-type context() :: proplists:proplist().
+-type auth() :: #a{}.
+-type user() :: any(). %% Opaque User Object
+-type client() :: any(). %% Opaque Client Object
+-type resowner() :: any(). %% Opaque Resource Owner Object
+-type rediruri() :: any(). %% Opaque Redirection URI
+-type device_id() :: any().
+-type token() :: binary().
+-type response() :: oauth2_response:response().
+-type lifetime() :: non_neg_integer().
+-type scope() :: list(binary()) | binary().
+-type appctx() :: term().
+-type error() :: access_denied | invalid_client | invalid_grant |
+ invalid_request | invalid_authorization | invalid_scope |
+ unauthorized_client | unsupported_grant_type |
+ unsupported_response_type | server_error |
+ temporarily_unavailable | atom().
%%%_* Code =============================================================
%%%_ * API -------------------------------------------------------------
@@ -209,7 +206,7 @@ authorize_code_request(User, Client, RedirUri, Scope, Ctx0) ->
%% @doc Sometimes one wishes to authorize directly with a specific scope and/or
%% a specific TTL, and this function is for that.
-spec authorize_directly(client(), resowner(), scope(), non_neg_integer()) ->
- {ok, auth()}.
+ auth().
authorize_directly(Client, ResOwner, Scope, TTL) ->
#a{ client = Client
, resowner = ResOwner
@@ -240,28 +237,16 @@ issue_code(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) ->
%% must be issued.
%% - 4.4.3. Client Credentials Grant > Access Token Response, with the
%% result of authorize_client_credentials/4.
--spec issue_token(auth(), appctx()) -> {ok, {appctx(), response()}}.
+-spec issue_token(auth(), appctx()) -> {ok, {appctx(), response()}} | {error, error()}.
issue_token(#a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}, Ctx0) ->
GrantContext = build_context(Client,seconds_since_epoch(TTL),Owner,Scope),
AccessToken = ?TOKEN:generate(GrantContext),
- {ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken
- , GrantContext
- , Ctx0 ),
- {ok, {Ctx1, oauth2_response:new(AccessToken, TTL, Owner, Scope)}}.
-
-%% @doc Issues an JWT without refresh token from an authorization.
--spec issue_jwt(auth(), appctx()) -> {ok, {appctx(), context(), response()}}.
-issue_jwt(#a{ client = Client
- , resowner = ResOwner
- , scope = Scope
- , ttl = TTL
- , issuer = Issuer}, Ctx) ->
- ExpiryTime = seconds_since_epoch(TTL),
- IssuedAt = seconds_since_epoch(0),
- AccessCtx = build_jwt_context( Issuer, ResOwner, ExpiryTime, IssuedAt
- , Client, Scope),
- {ok, JWT} = ?BACKEND:jwt_sign(AccessCtx, Ctx),
- {ok, {Ctx, AccessCtx, oauth2_response:new(JWT, TTL)}}.
+ case ?BACKEND:associate_access_token(AccessToken, GrantContext, Ctx0 ) of
+ {ok, Ctx1} ->
+ {ok, {Ctx1, oauth2_response:new(AccessToken, TTL, Owner, Scope)}};
+ {error, Reason} ->
+ {error, Reason}
+ end.
%% @doc Issues access and refresh tokens from an authorization.
%% Use it to implement the following steps of RFC 6749:
@@ -276,7 +261,7 @@ issue_token_and_refresh(#a{client = undefined}, _Ctx) ->
{error, invalid_authorization};
issue_token_and_refresh(#a{resowner = undefined}, _Ctx) ->
{error, invalid_authorization};
-issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}
+issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL, device_id = DeviceId}
, Ctx0 ) ->
RTTL = oauth2_config:expiry_time(refresh_token),
RefreshCtx = build_context(Client,seconds_since_epoch(RTTL),Owner,Scope),
@@ -286,9 +271,11 @@ issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}
{ok, Ctx1} = ?BACKEND:associate_access_token( AccessToken
, AccessCtx
, Ctx0),
- {ok, Ctx2} = ?BACKEND:associate_refresh_token( RefreshToken
- , RefreshCtx
- , Ctx1 ),
+ {ok, Ctx2} =
+ case DeviceId of
+ undefined -> ?BACKEND:associate_refresh_token(RefreshToken, RefreshCtx, Ctx1);
+ _ -> ?BACKEND:associate_refresh_token(RefreshToken, RefreshCtx, DeviceId, Ctx1)
+ end,
{ok, {Ctx2, oauth2_response:new( AccessToken
, TTL
, Owner
@@ -296,37 +283,6 @@ issue_token_and_refresh( #a{client=Client, resowner=Owner, scope=Scope, ttl=TTL}
, RefreshToken
, RTTL )}}.
-%% @doc Issues JWT and refresh token from an authorization.
--spec issue_jwt_and_refresh(auth(), binary(), appctx()) ->
- {ok, {appctx(), context(), response()}}
- | {error, invalid_authorization}.
-issue_jwt_and_refresh(#a{client = undefined}, _, _) ->
- {error, invalid_authorization};
-issue_jwt_and_refresh(#a{resowner = undefined}, _, _) ->
- {error, invalid_authorization};
-issue_jwt_and_refresh( #a{ client = Client
- , resowner = ResOwner
- , scope = Scope
- , ttl = TTL
- , issuer = Issuer}
- , DeviceId
- , Ctx0) ->
- % access_token
- AccessExpiry = seconds_since_epoch(TTL),
- IssuedAt = seconds_since_epoch(0),
- AccessCtx = build_jwt_context( Issuer, ResOwner, AccessExpiry, IssuedAt
- , Client, Scope),
- {ok, JWT} = ?BACKEND:jwt_sign(AccessCtx, Ctx0),
-
- % refresh_token
- RefreshTTL = oauth2_config:expiry_time(jwt_refresh_token),
- RefreshExpiry = seconds_since_epoch(RefreshTTL),
- RefreshCtx = build_context(Client, RefreshExpiry, ResOwner, Scope),
- RefreshToken = ?TOKEN:generate(RefreshCtx),
- {ok, Ctx1} = ?BACKEND:associate_refresh_token( RefreshToken, RefreshCtx
- , DeviceId, Ctx0),
- {ok, {Ctx1, AccessCtx, oauth2_response:new(JWT, TTL, RefreshToken)}}.
-
%% @doc Verifies an access code AccessCode, returning its associated
%% context if successful. Otherwise, an OAuth2 error code is returned.
-spec verify_access_code(token(), appctx()) -> {ok, {appctx(), context()}}
@@ -366,46 +322,16 @@ verify_access_code(AccessCode, Client, Ctx0) ->
-> {ok, {appctx(), response()}} | {error, error()}.
refresh_access_token(Client, RefreshToken, Scope, Ctx0) ->
case verify_refresh_token_basic(Client, RefreshToken, Scope, Ctx0) of
- {ok, {Ctx1, ClientId, ResOwner, VerifiedScope, TTL, _DeviceId}} ->
+ {ok, {Ctx1, ClientId, ResOwner, VerifiedScope, TTL, DeviceId}} ->
issue_token(#a{ client = ClientId
, resowner = ResOwner
, scope = VerifiedScope
, ttl = TTL
+ , device_id = DeviceId
}, Ctx1);
{error, _} = E -> E
end.
-%% @doc Validates a request for a JWT from a refresh token, issuing a new JWT
-%% if valid.
--spec refresh_jwt(client(), token(), scope(), appctx()) ->
- {ok, {appctx(), context(), response()}}
- | {error, error()}.
-refresh_jwt(Client, RefreshToken, Scope, Ctx0) ->
- case verify_refresh_token_basic(Client, RefreshToken, Scope, Ctx0) of
- {ok, {Ctx1, ClientId, ResOwner, VerifiedScope, TTL, DeviceId}} ->
- % RFC 6749 Section 10.4 (Security Considerations for refresh_token)
- %
- % Authorization server could employ refresh token rotation in which
- % a new refresh token is issued with every access token refresh
- % response. The previous refresh token is invalidated but retained
- % by the authorization server. If a refresh token is compromised
- % and subsequently used by both the attacker and the legitimate
- % client, one of them will present an invalidated refresh token,
- % which will inform the authorization server of the breach.
- %
- % TODO: implement this
- ?BACKEND:revoke_refresh_token(RefreshToken, Ctx1),
- issue_jwt_and_refresh( #a{ client = ClientId
- , resowner = ResOwner
- , scope = VerifiedScope
- , ttl = TTL
- , issuer = ?BACKEND:jwt_issuer()
- }
- , DeviceId
- , Ctx1);
- {error, _} = E -> E
- end.
-
%% @doc Verifies an access token AccessToken, returning its associated
%% context if successful. Otherwise, an OAuth2 error code is returned.
-spec verify_access_token(token(), appctx()) -> {ok, {appctx(), context()}}
@@ -422,19 +348,6 @@ verify_access_token(AccessToken, Ctx0) ->
end
end.
-%% @doc Verifies a JWT, returning its associated context if successful.
-%% Otherwise, an OAuth2 error code is returned.
--spec verify_jwt(token()) -> {ok, context()} | {error, error()}.
-verify_jwt(JWT) ->
- case ?BACKEND:jwt_verify(JWT) of
- {error, _} -> {error, access_denied};
- {ok, GrantCtx} ->
- case get_(GrantCtx, <<"exp">>) > seconds_since_epoch(0) of
- true -> {ok, GrantCtx};
- false -> {error, access_denied}
- end
- end.
-
%%%_* Private functions ================================================
auth_user(User, Scope0, Ctx0) ->
case ?BACKEND:authenticate_user(User, Ctx0) of
@@ -447,7 +360,6 @@ auth_user(User, Scope0, Ctx0) ->
, scope = Scope1
, ttl = oauth2_config:expiry_time(
password_credentials)
- , issuer = ?BACKEND:jwt_issuer()
}}}
end
end.
@@ -480,7 +392,7 @@ verify_refresh_token_basic(Client, RefreshToken, Scope, Ctx0) ->
{ok, {Ctx3, VerifiedScope}} ->
{ok, ClientId} = get(GrantCtx, <<"client">>),
{ok, ResOwner} = get(GrantCtx, <<"resource_owner">>),
- {ok, DeviceId} = get(GrantCtx, <<"device_id">>),
+ DeviceId = get(GrantCtx, <<"device_id">>, undefined),
TTL = oauth2_config:expiry_time(
password_credentials),
{ok, { Ctx3, ClientId, ResOwner
@@ -504,15 +416,6 @@ build_context(Client, ExpiryTime, ResOwner, Scope) ->
build_context(Client, ExpiryTime, ResOwner, Scope, RefreshToken) ->
[{<<"refresh_token">>, RefreshToken} | build_context(Client, ExpiryTime, ResOwner, Scope)].
-build_jwt_context(Issuer, ResOwner, ExpiryTime, IssuedAt, Client, Scope) ->
- [ {<<"iss">>, Issuer}
- , {<<"sub">>, ResOwner}
- , {<<"exp">>, ExpiryTime}
- , {<<"iat">>, IssuedAt}
- , {<<"client">>, Client}
- , {<<"scope">>, Scope}
- ].
-
-spec seconds_since_epoch(integer()) -> non_neg_integer().
seconds_since_epoch(Diff) ->
{Mega, Secs, _} = os:timestamp(),
@@ -524,6 +427,12 @@ get(O, K) ->
false -> {error, notfound}
end.
+get(O, K, D) ->
+ case get(O, K) of
+ {ok, V} -> V;
+ {error, notfound} -> D
+ end.
+
get_(O, K) ->
{ok, V} = get(O, K),
V.
diff --git a/src/oauth2_backend.erl b/src/oauth2_backend.erl
index edcbd10..1377d84 100644
--- a/src/oauth2_backend.erl
+++ b/src/oauth2_backend.erl
@@ -112,18 +112,6 @@
-callback verify_scope(scope(), scope(), appctx()) ->
{ok, {appctx(), scope()}} | {error, notfound | badscope}.
-%% @doc Sign the grant context with a private key and produce a JWT.
-%% The grant context is a proplist carrying information about the identity
-%% with which the token is associated, when it expires, etc.
--callback jwt_sign(grantctx(), appctx()) -> {ok, token()}.
-
-%% @doc Verifies a JWT, returning the corresponding grant context if
-%% verification succeeds.
--callback jwt_verify(token()) -> {ok, grantctx()} | {error, badjwt}.
-
-%% @doc A case-sensitive string or URI that uniquely identifies the issuer.
--callback jwt_issuer() -> binary().
-
%%%_* Tests ============================================================
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
diff --git a/src/oauth2_response.erl b/src/oauth2_response.erl
index 286f6be..f4e32d5 100644
--- a/src/oauth2_response.erl
+++ b/src/oauth2_response.erl
@@ -151,7 +151,7 @@ refresh_token(#response{refresh_token = RefreshToken}) -> {ok, RefreshToken}.
refresh_token(Response, NewRefreshToken) ->
Response#response{refresh_token = NewRefreshToken}.
--spec refresh_token_expires_in(response()) -> {ok, lifetime()} | {error, not_set}.
+-spec refresh_token_expires_in(response()) -> {ok, undefined | lifetime()} | {error, not_set}.
refresh_token_expires_in(#response{refresh_token = undefined}) ->
{error, not_set};
refresh_token_expires_in(#response{refresh_token_expires_in = RefreshTokenExpiresIn}) ->
@@ -218,15 +218,21 @@ to_binary([Binary]) when is_binary(Binary) ->
to_binary([BinaryHead | Tail]) when is_binary(BinaryHead) ->
<>;
to_binary(List) when is_list(List) ->
- to_binary(list_to_binary(List));
+ erlang:display({list, List}),
+ try
+ to_binary(list_to_binary(List))
+ catch
+ error:badarg ->
+ to_binary(lists:flatten(lists:map(fun to_binary/1, List)))
+ end;
to_binary(Atom) when is_atom(Atom) ->
to_binary(atom_to_list(Atom));
to_binary(Float) when is_float(Float) ->
to_binary(float_to_list(Float));
to_binary(Integer) when is_integer(Integer) ->
to_binary(integer_to_list(Integer));
-to_binary({Key, Value}) ->
- {to_binary(Key), to_binary(Value)};
+to_binary({_Key, _Value} = Tuple) ->
+ to_binary(tuple_to_list(Tuple));
to_binary(Term) ->
to_binary(term_to_binary(Term)).
diff --git a/test/oauth2_mock_backend.erl b/test/oauth2_mock_backend.erl
index eb65743..6e3919d 100644
--- a/test/oauth2_mock_backend.erl
+++ b/test/oauth2_mock_backend.erl
@@ -34,6 +34,7 @@
-export([get_client_identity/2]).
-export([associate_access_code/3]).
-export([associate_refresh_token/3]).
+-export([associate_refresh_token/4]).
-export([associate_access_token/3]).
-export([resolve_access_code/2]).
-export([resolve_refresh_token/2]).
@@ -86,6 +87,10 @@ associate_refresh_token(RefreshToken, Context, AppContext) ->
ets:insert(?ETS_TABLE, {RefreshToken, Context}),
{ok, AppContext}.
+associate_refresh_token(RefreshToken, Context, DeviceId, AppContext) ->
+ ets:insert(?ETS_TABLE, {RefreshToken, [{<<"device_id">>, DeviceId} | Context ]}),
+ {ok, AppContext}.
+
associate_access_token(AccessToken, Context, AppContext) ->
ets:insert(?ETS_TABLE, {AccessToken, Context}),
{ok, AppContext}.