Refactored TokenStorageInterface for an improved flow

* Now passing an auth code data validation callback to the exchange method
* Removed Token, it’s no longer necessary
* Simplified interface where possible
* All tests passing
* Updated docblocks
This commit is contained in:
Barnaby Walters 2021-06-13 14:34:37 +02:00
parent 645ab833c5
commit 61aa7f55f9
15 changed files with 468 additions and 335 deletions

View File

@ -148,6 +148,8 @@
self::INVALID_STATE => ['statusCode' => 302, 'name' => 'Invalid state Parameter', 'error' => 'invalid_request'], self::INVALID_STATE => ['statusCode' => 302, 'name' => 'Invalid state Parameter', 'error' => 'invalid_request'],
self::INVALID_CODE_CHALLENGE => ['statusCode' => 302, 'name' => 'Invalid code_challenge Parameter', 'error' => 'invalid_request'], self::INVALID_CODE_CHALLENGE => ['statusCode' => 302, 'name' => 'Invalid code_challenge Parameter', 'error' => 'invalid_request'],
self::INVALID_SCOPE => ['statusCode' => 302, 'name' => 'Invalid scope Parameter', 'error' => 'invalid_request'], self::INVALID_SCOPE => ['statusCode' => 302, 'name' => 'Invalid scope Parameter', 'error' => 'invalid_request'],
self::INVALID_GRANT => ['statusCode' => 400, 'name' => 'The provided credentials were not valid.', 'error' => 'invalid_grant'],
self::INVALID_REQUEST => ['statusCode' => 400, 'name' => 'Invalid Request', 'error' => 'invalid_request'],
] </span> ] </span>
</dt> </dt>
<dd></dd> <dd></dd>
@ -192,6 +194,13 @@
<span> <span>
&nbsp;= 10 </span> &nbsp;= 10 </span>
</dt> </dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public">
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_GRANT">INVALID_GRANT</a>
<span>
&nbsp;= 12 </span>
</dt>
<dd></dd> <dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public"> <dt class="phpdocumentor-table-of-contents__entry -constant -public">
@ -199,6 +208,13 @@
<span> <span>
&nbsp;= 7 </span> &nbsp;= 7 </span>
</dt> </dt>
<dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public">
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_REQUEST">INVALID_REQUEST</a>
<span>
&nbsp;= 13 </span>
</dt>
<dd></dd> <dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -constant -public"> <dt class="phpdocumentor-table-of-contents__entry -constant -public">
@ -358,7 +374,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">23</span> <span class="phpdocumentor-element-found-in__line">25</span>
</aside> </aside>
@ -380,6 +396,8 @@
self::INVALID_STATE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid state Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;], self::INVALID_STATE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid state Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
self::INVALID_CODE_CHALLENGE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid code_challenge Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;], self::INVALID_CODE_CHALLENGE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid code_challenge Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
self::INVALID_SCOPE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid scope Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;], self::INVALID_SCOPE =&gt; [&#039;statusCode&#039; =&gt; 302, &#039;name&#039; =&gt; &#039;Invalid scope Parameter&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
self::INVALID_GRANT =&gt; [&#039;statusCode&#039; =&gt; 400, &#039;name&#039; =&gt; &#039;The provided credentials were not valid.&#039;, &#039;error&#039; =&gt; &#039;invalid_grant&#039;],
self::INVALID_REQUEST =&gt; [&#039;statusCode&#039; =&gt; 400, &#039;name&#039; =&gt; &#039;Invalid Request&#039;, &#039;error&#039; =&gt; &#039;invalid_request&#039;],
]</span> ]</span>
</code> </code>
@ -537,6 +555,31 @@
</article>
<article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_GRANT">
INVALID_GRANT
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_GRANT" class="headerlink"><i class="fas fa-link"></i></a>
</h4>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">22</span>
</aside>
<code class="phpdocumentor-signature phpdocumentor-code ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__type">mixed</span>
<span class="phpdocumentor-signature__name">INVALID_GRANT</span>
= <span class="phpdocumentor-signature__default-value">12</span>
</code>
</article> </article>
<article class="phpdocumentor-element -constant -public "> <article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_REDIRECT_URI"> <h4 class="phpdocumentor-element__name" id="constant_INVALID_REDIRECT_URI">
@ -562,6 +605,31 @@
</article>
<article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_REQUEST">
INVALID_REQUEST
<a href="classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_REQUEST" class="headerlink"><i class="fas fa-link"></i></a>
</h4>
<aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
:
<span class="phpdocumentor-element-found-in__line">23</span>
</aside>
<code class="phpdocumentor-signature phpdocumentor-code ">
<span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__type">mixed</span>
<span class="phpdocumentor-signature__name">INVALID_REQUEST</span>
= <span class="phpdocumentor-signature__default-value">13</span>
</code>
</article> </article>
<article class="phpdocumentor-element -constant -public "> <article class="phpdocumentor-element -constant -public ">
<h4 class="phpdocumentor-element__name" id="constant_INVALID_SCOPE"> <h4 class="phpdocumentor-element__name" id="constant_INVALID_SCOPE">
@ -637,7 +705,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">39</span> <span class="phpdocumentor-element-found-in__line">43</span>
</aside> </aside>
@ -671,7 +739,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">41</span> <span class="phpdocumentor-element-found-in__line">45</span>
</aside> </aside>
@ -727,7 +795,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">56</span> <span class="phpdocumentor-element-found-in__line">60</span>
</aside> </aside>
@ -759,7 +827,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">60</span> <span class="phpdocumentor-element-found-in__line">64</span>
</aside> </aside>
@ -791,7 +859,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">76</span> <span class="phpdocumentor-element-found-in__line">80</span>
</aside> </aside>
@ -823,7 +891,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">52</span> <span class="phpdocumentor-element-found-in__line">56</span>
</aside> </aside>
@ -855,7 +923,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/IndieAuthException.php"><a href="files/src-indieauthexception.html"><abbr title="src/IndieAuthException.php">IndieAuthException.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">71</span> <span class="phpdocumentor-element-found-in__line">75</span>
</aside> </aside>
<p class="phpdocumentor-summary">Trust Query Params</p> <p class="phpdocumentor-summary">Trust Query Params</p>

View File

@ -1072,7 +1072,7 @@ error behaviour, one way to do so is to subclass <code class="prettyprint">Serve
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Server.php"><a href="files/src-server.html"><abbr title="src/Server.php">Server.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Server.php"><a href="files/src-server.html"><abbr title="src/Server.php">Server.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">769</span> <span class="phpdocumentor-element-found-in__line">771</span>
</aside> </aside>
<p class="phpdocumentor-summary">Handle Exception</p> <p class="phpdocumentor-summary">Handle Exception</p>

View File

@ -93,7 +93,7 @@
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">25</span> <span class="phpdocumentor-element-found-in__line">26</span>
</aside> </aside>
<p class="phpdocumentor-summary">Filesystem JSON Token Storage</p> <p class="phpdocumentor-summary">Filesystem JSON Token Storage</p>
@ -199,7 +199,7 @@ will likely be superceded by the SQLite version.</p>
<dt class="phpdocumentor-table-of-contents__entry -method -public"> <dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_createAuthCode">createAuthCode()</a> <a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_createAuthCode">createAuthCode()</a>
<span> <span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span> &nbsp;: string|null </span>
</dt> </dt>
<dd>Create Auth Code</dd> <dd>Create Auth Code</dd>
@ -220,7 +220,7 @@ will likely be superceded by the SQLite version.</p>
<dt class="phpdocumentor-table-of-contents__entry -method -public"> <dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_exchangeAuthCodeForAccessToken">exchangeAuthCodeForAccessToken()</a> <a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_exchangeAuthCodeForAccessToken">exchangeAuthCodeForAccessToken()</a>
<span> <span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span> &nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt> </dt>
<dd>Exchange Authorization Code for Access Token</dd> <dd>Exchange Authorization Code for Access Token</dd>
@ -234,7 +234,7 @@ will likely be superceded by the SQLite version.</p>
<dt class="phpdocumentor-table-of-contents__entry -method -public"> <dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_getAccessToken">getAccessToken()</a> <a href="classes/Taproot-IndieAuth-Storage-FilesystemJsonStorage.html#method_getAccessToken">getAccessToken()</a>
<span> <span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span> &nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt> </dt>
<dd>Get Access Token</dd> <dd>Get Access Token</dd>
@ -299,7 +299,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">27</span> <span class="phpdocumentor-element-found-in__line">28</span>
</aside> </aside>
@ -324,7 +324,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">26</span> <span class="phpdocumentor-element-found-in__line">27</span>
</aside> </aside>
@ -349,7 +349,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">29</span> <span class="phpdocumentor-element-found-in__line">30</span>
</aside> </aside>
@ -389,7 +389,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">33</span> <span class="phpdocumentor-element-found-in__line">34</span>
</aside> </aside>
@ -419,7 +419,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">32</span> <span class="phpdocumentor-element-found-in__line">33</span>
</aside> </aside>
@ -449,7 +449,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">36</span> <span class="phpdocumentor-element-found-in__line">37</span>
</aside> </aside>
@ -479,7 +479,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">31</span> <span class="phpdocumentor-element-found-in__line">32</span>
</aside> </aside>
@ -509,7 +509,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">34</span> <span class="phpdocumentor-element-found-in__line">35</span>
</aside> </aside>
@ -543,7 +543,7 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">39</span> <span class="phpdocumentor-element-found-in__line">40</span>
</aside> </aside>
@ -620,14 +620,14 @@ will likely be superceded by the SQLite version.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">67</span> <span class="phpdocumentor-element-found-in__line">68</span>
</aside> </aside>
<p class="phpdocumentor-summary">Create Auth Code</p> <p class="phpdocumentor-summary">Create Auth Code</p>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code> <span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">string|null</span></code>
<section class="phpdocumentor-description"><p>This method is called on a valid authorization token request. The <code class="prettyprint">$data</code> <section class="phpdocumentor-description"><p>This method is called on a valid authorization token request. The <code class="prettyprint">$data</code>
array is guaranteed to have the following keys:</p> array is guaranteed to have the following keys:</p>
@ -659,9 +659,8 @@ key, which is a valid space-separated scope string listing the scopes granted by
the consent screen. Other implementations of <code class="prettyprint">AuthorizationFormInterface</code> may add additional the consent screen. Other implementations of <code class="prettyprint">AuthorizationFormInterface</code> may add additional
data, such as custom token-specific settings, or a custom token lifetime.</li> data, such as custom token-specific settings, or a custom token lifetime.</li>
</ul> </ul>
<p>This method should store the data passed to it, generate a corresponding authorization code, <p>This method should store the data passed to it, generate a corresponding authorization code
and return an instance of <code class="prettyprint">Storage\Token</code> containing both. Implementations will usually add string, and return it.</p>
an expiry time, usually under the <code class="prettyprint">valid_until</code> key.</p>
<p>The method call and data is structured such that implementations have a lot of flexibility <p>The method call and data is structured such that implementations have a lot of flexibility
about how to store authorization code data. It could be a record in an auth code database about how to store authorization code data. It could be a record in an auth code database
table, a record in a table which is used for both auth codes and access tokens, or even table, a record in a table which is used for both auth codes and access tokens, or even
@ -687,7 +686,7 @@ throw exceptions.</p>
<h5 class="phpdocumentor-return-value__heading">Return values</h5> <h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span> <span class="phpdocumentor-signature__response_type">string|null</span>
&mdash; &mdash;
<section class="phpdocumentor-description"></section> <section class="phpdocumentor-description"></section>
@ -706,13 +705,13 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">208</span> <span class="phpdocumentor-element-found-in__line">228</span>
</aside> </aside>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">delete</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$key</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">bool</span></code> <span class="phpdocumentor-signature__name">delete</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$key</span></span><span class="phpdocumentor-signature__argument"><span>[</span><span>, </span><span class="phpdocumentor-signature__argument__return-type">mixed&nbsp;</span><span class="phpdocumentor-signature__argument__name">$observeLock</span><span> = </span><span class="phpdocumentor-signature__argument__default-value">true</span><span> ]</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">bool</span></code>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5> <h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -723,6 +722,13 @@ throw exceptions.</p>
</dt> </dt>
<dd class="phpdocumentor-argument-list__definition"> <dd class="phpdocumentor-argument-list__definition">
</dd>
<dt class="phpdocumentor-argument-list__entry">
<span class="phpdocumentor-signature__argument__name">$observeLock</span>
: <span class="phpdocumentor-signature__argument__return-type">mixed</span>
= <span class="phpdocumentor-signature__argument__default-value">true</span> </dt>
<dd class="phpdocumentor-argument-list__definition">
</dd> </dd>
</dl> </dl>
@ -748,7 +754,7 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">149</span> <span class="phpdocumentor-element-found-in__line">169</span>
</aside> </aside>
@ -780,29 +786,56 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">81</span> <span class="phpdocumentor-element-found-in__line">82</span>
</aside> </aside>
<p class="phpdocumentor-summary">Exchange Authorization Code for Access Token</p> <p class="phpdocumentor-summary">Exchange Authorization Code for Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code> <span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span class="phpdocumentor-signature__argument"><span>, </span><span class="phpdocumentor-signature__argument__return-type">callable&nbsp;</span><span class="phpdocumentor-signature__argument__name">$validateAuthCode</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Attempt to exchange an authorization code identified by <code class="prettyprint">$code</code> for <section class="phpdocumentor-description"><p>Attempt to exchange an authorization code identified by <code class="prettyprint">$code</code> for
an access token, returning it in a <code class="prettyprint">Token</code> on success and null on error.</p> an access token. Return an array of access token data to be passed onto
the client app on success, and null on error.</p>
<p>This method is called at the beginning of a code exchange request, before <p>This method is called at the beginning of a code exchange request, before
further error checking or validation is applied. On an error, the created further error checking or validation is applied. It should proceed as
access token is immediately revoked via <code class="prettyprint">revokeAccessToken()</code>.</p> follows.</p>
<p>For this reason, the token data in the returned Token object MUST include <ul>
the <code class="prettyprint">client_id</code> and <code class="prettyprint">redirect_uri</code> parameters associated with the <li>Attempt to fetch the authorization code data identified by $code. If
authorization code, as these are used by the IndieAuth Server for further it does not exist or has expired, return null;</li>
validation.</p> <li>Pass the authorization code data array to $validateAuthCode for validation.
<p>This method is responsible for ensuring that the matched auth code is If there is a problem with the code, a <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code>
not expired. If it is, it should return null, presumably after deleting will be thrown. This method should catch it, invalidate the authorization
the corresponding authorization code record.</p> code data, then re-throw the exception for handling by Server.</li>
<p>If the exchanged access token should expire, this method should set its <li>If the authorization code data passed all checks, convert it into an access
expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p> token, invalidate the auth code to prevent re-use, and store the access token
data internally.</li>
<li>Return an array of access token data to be passed onto the client app. It MUST
contain the following keys:
<ul>
<li>
<code class="prettyprint">me</code>
</li>
<li>
<code class="prettyprint">access_token</code>
Additonally, it SHOULD contain the following keys:</li>
<li>
<code class="prettyprint">scope</code>, if the token grants any scope
And MAY contain additional keys, such as:</li>
<li>
<code class="prettyprint">profile</code>
</li>
<li>
<code class="prettyprint">expires_at</code>
</li>
</ul>
</li>
</ul>
<p>If the authorization code was redeemed at the authorization endpoint, Server will
only pass the <code class="prettyprint">me</code> and <code class="prettyprint">profile</code> keys onto the client. In both cases, it will filter
out <code class="prettyprint">code_challenge</code> keys to prevent that data from accidentally being leaked to
clients.</p>
</section> </section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5> <h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -812,16 +845,28 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
: <span class="phpdocumentor-signature__argument__return-type">string</span> : <span class="phpdocumentor-signature__argument__return-type">string</span>
</dt> </dt>
<dd class="phpdocumentor-argument-list__definition"> <dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>The Authorization Code to attempt to exchange.</p>
</section>
</dd>
<dt class="phpdocumentor-argument-list__entry">
<span class="phpdocumentor-signature__argument__name">$validateAuthCode</span>
: <span class="phpdocumentor-signature__argument__return-type">callable</span>
</dt>
<dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>A callable to perform additional validation if valid auth code data is found. Takes <code class="prettyprint">array $authCodeData</code>, raises <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code> on invalid data, which should be bubbled up to the caller after any clean-up. Returns void.</p>
</section>
</dd> </dd>
</dl> </dl>
<h5 class="phpdocumentor-return-value__heading">Return values</h5> <h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span> <span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash; &mdash;
<section class="phpdocumentor-description"></section> <section class="phpdocumentor-description"><p>An array of access token data to return to the client on success, null on any error.</p>
</section>
</article> </article>
@ -838,7 +883,7 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">177</span> <span class="phpdocumentor-element-found-in__line">197</span>
</aside> </aside>
@ -880,18 +925,17 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">127</span> <span class="phpdocumentor-element-found-in__line">147</span>
</aside> </aside>
<p class="phpdocumentor-summary">Get Access Token</p> <p class="phpdocumentor-summary">Get Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code> <span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Fetch access token data identified by the token <code class="prettyprint">$token</code>, returning <section class="phpdocumentor-description"><p>Fetch access token data identified by the token <code class="prettyprint">$token</code>, returning
null if it is expired or invalid. The data should be structured in null if it is expired or invalid.</p>
exactly the same way it was stored by <code class="prettyprint">exchangeAuthCodeForAccessToken</code>.</p>
</section> </section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5> <h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -908,7 +952,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<h5 class="phpdocumentor-return-value__heading">Return values</h5> <h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span> <span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash; &mdash;
<section class="phpdocumentor-description"></section> <section class="phpdocumentor-description"></section>
@ -927,7 +971,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">218</span> <span class="phpdocumentor-element-found-in__line">242</span>
</aside> </aside>
@ -969,7 +1013,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">199</span> <span class="phpdocumentor-element-found-in__line">219</span>
</aside> </aside>
@ -1018,7 +1062,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">143</span> <span class="phpdocumentor-element-found-in__line">163</span>
</aside> </aside>
<p class="phpdocumentor-summary">Revoke Access Token</p> <p class="phpdocumentor-summary">Revoke Access Token</p>
@ -1064,7 +1108,7 @@ or false on error, including if the token did not exist.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">61</span> <span class="phpdocumentor-element-found-in__line">62</span>
</aside> </aside>
@ -1106,7 +1150,7 @@ or false on error, including if the token did not exist.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">247</span> <span class="phpdocumentor-element-found-in__line">271</span>
</aside> </aside>
@ -1148,7 +1192,7 @@ or false on error, including if the token did not exist.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/FilesystemJsonStorage.php"><a href="files/src-storage-filesystemjsonstorage.html"><abbr title="src/Storage/FilesystemJsonStorage.php">FilesystemJsonStorage.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">223</span> <span class="phpdocumentor-element-found-in__line">247</span>
</aside> </aside>

View File

@ -133,21 +133,21 @@ reason, is indicated by returning either <code class="prettyprint">null</code> o
<dt class="phpdocumentor-table-of-contents__entry -method -public"> <dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_createAuthCode">createAuthCode()</a> <a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_createAuthCode">createAuthCode()</a>
<span> <span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span> &nbsp;: string|null </span>
</dt> </dt>
<dd>Create Auth Code</dd> <dd>Create Auth Code</dd>
<dt class="phpdocumentor-table-of-contents__entry -method -public"> <dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_exchangeAuthCodeForAccessToken">exchangeAuthCodeForAccessToken()</a> <a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_exchangeAuthCodeForAccessToken">exchangeAuthCodeForAccessToken()</a>
<span> <span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span> &nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt> </dt>
<dd>Exchange Authorization Code for Access Token</dd> <dd>Exchange Authorization Code for Access Token</dd>
<dt class="phpdocumentor-table-of-contents__entry -method -public"> <dt class="phpdocumentor-table-of-contents__entry -method -public">
<a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_getAccessToken">getAccessToken()</a> <a href="classes/Taproot-IndieAuth-Storage-TokenStorageInterface.html#method_getAccessToken">getAccessToken()</a>
<span> <span>
&nbsp;: <a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null </span> &nbsp;: array&lt;string|int, mixed&gt;|null </span>
</dt> </dt>
<dd>Get Access Token</dd> <dd>Get Access Token</dd>
@ -182,14 +182,14 @@ reason, is indicated by returning either <code class="prettyprint">null</code> o
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">79</span> <span class="phpdocumentor-element-found-in__line">78</span>
</aside> </aside>
<p class="phpdocumentor-summary">Create Auth Code</p> <p class="phpdocumentor-summary">Create Auth Code</p>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code> <span class="phpdocumentor-signature__name">createAuthCode</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">array&lt;string|int, mixed&gt;&nbsp;</span><span class="phpdocumentor-signature__argument__name">$data</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">string|null</span></code>
<section class="phpdocumentor-description"><p>This method is called on a valid authorization token request. The <code class="prettyprint">$data</code> <section class="phpdocumentor-description"><p>This method is called on a valid authorization token request. The <code class="prettyprint">$data</code>
array is guaranteed to have the following keys:</p> array is guaranteed to have the following keys:</p>
@ -221,9 +221,8 @@ key, which is a valid space-separated scope string listing the scopes granted by
the consent screen. Other implementations of <code class="prettyprint">AuthorizationFormInterface</code> may add additional the consent screen. Other implementations of <code class="prettyprint">AuthorizationFormInterface</code> may add additional
data, such as custom token-specific settings, or a custom token lifetime.</li> data, such as custom token-specific settings, or a custom token lifetime.</li>
</ul> </ul>
<p>This method should store the data passed to it, generate a corresponding authorization code, <p>This method should store the data passed to it, generate a corresponding authorization code
and return an instance of <code class="prettyprint">Storage\Token</code> containing both. Implementations will usually add string, and return it.</p>
an expiry time, usually under the <code class="prettyprint">valid_until</code> key.</p>
<p>The method call and data is structured such that implementations have a lot of flexibility <p>The method call and data is structured such that implementations have a lot of flexibility
about how to store authorization code data. It could be a record in an auth code database about how to store authorization code data. It could be a record in an auth code database
table, a record in a table which is used for both auth codes and access tokens, or even table, a record in a table which is used for both auth codes and access tokens, or even
@ -249,7 +248,7 @@ throw exceptions.</p>
<h5 class="phpdocumentor-return-value__heading">Return values</h5> <h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span> <span class="phpdocumentor-signature__response_type">string|null</span>
&mdash; &mdash;
<section class="phpdocumentor-description"></section> <section class="phpdocumentor-description"></section>
@ -268,29 +267,56 @@ throw exceptions.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">103</span> <span class="phpdocumentor-element-found-in__line">119</span>
</aside> </aside>
<p class="phpdocumentor-summary">Exchange Authorization Code for Access Token</p> <p class="phpdocumentor-summary">Exchange Authorization Code for Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code> <span class="phpdocumentor-signature__name">exchangeAuthCodeForAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$code</span></span><span class="phpdocumentor-signature__argument"><span>, </span><span class="phpdocumentor-signature__argument__return-type">callable&nbsp;</span><span class="phpdocumentor-signature__argument__name">$validateAuthCode</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Attempt to exchange an authorization code identified by <code class="prettyprint">$code</code> for <section class="phpdocumentor-description"><p>Attempt to exchange an authorization code identified by <code class="prettyprint">$code</code> for
an access token, returning it in a <code class="prettyprint">Token</code> on success and null on error.</p> an access token. Return an array of access token data to be passed onto
the client app on success, and null on error.</p>
<p>This method is called at the beginning of a code exchange request, before <p>This method is called at the beginning of a code exchange request, before
further error checking or validation is applied. On an error, the created further error checking or validation is applied. It should proceed as
access token is immediately revoked via <code class="prettyprint">revokeAccessToken()</code>.</p> follows.</p>
<p>For this reason, the token data in the returned Token object MUST include <ul>
the <code class="prettyprint">client_id</code> and <code class="prettyprint">redirect_uri</code> parameters associated with the <li>Attempt to fetch the authorization code data identified by $code. If
authorization code, as these are used by the IndieAuth Server for further it does not exist or has expired, return null;</li>
validation.</p> <li>Pass the authorization code data array to $validateAuthCode for validation.
<p>This method is responsible for ensuring that the matched auth code is If there is a problem with the code, a <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code>
not expired. If it is, it should return null, presumably after deleting will be thrown. This method should catch it, invalidate the authorization
the corresponding authorization code record.</p> code data, then re-throw the exception for handling by Server.</li>
<p>If the exchanged access token should expire, this method should set its <li>If the authorization code data passed all checks, convert it into an access
expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p> token, invalidate the auth code to prevent re-use, and store the access token
data internally.</li>
<li>Return an array of access token data to be passed onto the client app. It MUST
contain the following keys:
<ul>
<li>
<code class="prettyprint">me</code>
</li>
<li>
<code class="prettyprint">access_token</code>
Additonally, it SHOULD contain the following keys:</li>
<li>
<code class="prettyprint">scope</code>, if the token grants any scope
And MAY contain additional keys, such as:</li>
<li>
<code class="prettyprint">profile</code>
</li>
<li>
<code class="prettyprint">expires_at</code>
</li>
</ul>
</li>
</ul>
<p>If the authorization code was redeemed at the authorization endpoint, Server will
only pass the <code class="prettyprint">me</code> and <code class="prettyprint">profile</code> keys onto the client. In both cases, it will filter
out <code class="prettyprint">code_challenge</code> keys to prevent that data from accidentally being leaked to
clients.</p>
</section> </section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5> <h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -300,16 +326,28 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
: <span class="phpdocumentor-signature__argument__return-type">string</span> : <span class="phpdocumentor-signature__argument__return-type">string</span>
</dt> </dt>
<dd class="phpdocumentor-argument-list__definition"> <dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>The Authorization Code to attempt to exchange.</p>
</section>
</dd>
<dt class="phpdocumentor-argument-list__entry">
<span class="phpdocumentor-signature__argument__name">$validateAuthCode</span>
: <span class="phpdocumentor-signature__argument__return-type">callable</span>
</dt>
<dd class="phpdocumentor-argument-list__definition">
<section class="phpdocumentor-description"><p>A callable to perform additional validation if valid auth code data is found. Takes <code class="prettyprint">array $authCodeData</code>, raises <code class="prettyprint">Taproot\IndieAuth\IndieAuthException</code> on invalid data, which should be bubbled up to the caller after any clean-up. Returns void.</p>
</section>
</dd> </dd>
</dl> </dl>
<h5 class="phpdocumentor-return-value__heading">Return values</h5> <h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span> <span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash; &mdash;
<section class="phpdocumentor-description"></section> <section class="phpdocumentor-description"><p>An array of access token data to return to the client on success, null on any error.</p>
</section>
</article> </article>
@ -326,18 +364,17 @@ expiry time, usually in a <code class="prettyprint">valid_until</code> key.</p>
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">112</span> <span class="phpdocumentor-element-found-in__line">127</span>
</aside> </aside>
<p class="phpdocumentor-summary">Get Access Token</p> <p class="phpdocumentor-summary">Get Access Token</p>
<code class="phpdocumentor-code phpdocumentor-signature "> <code class="phpdocumentor-code phpdocumentor-signature ">
<span class="phpdocumentor-signature__visibility">public</span> <span class="phpdocumentor-signature__visibility">public</span>
<span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span></code> <span class="phpdocumentor-signature__name">getAccessToken</span><span>(</span><span class="phpdocumentor-signature__argument"><span class="phpdocumentor-signature__argument__return-type">string&nbsp;</span><span class="phpdocumentor-signature__argument__name">$token</span></span><span>)</span><span> : </span><span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span></code>
<section class="phpdocumentor-description"><p>Fetch access token data identified by the token <code class="prettyprint">$token</code>, returning <section class="phpdocumentor-description"><p>Fetch access token data identified by the token <code class="prettyprint">$token</code>, returning
null if it is expired or invalid. The data should be structured in null if it is expired or invalid.</p>
exactly the same way it was stored by <code class="prettyprint">exchangeAuthCodeForAccessToken</code>.</p>
</section> </section>
<h5 class="phpdocumentor-argument-list__heading">Parameters</h5> <h5 class="phpdocumentor-argument-list__heading">Parameters</h5>
@ -354,7 +391,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<h5 class="phpdocumentor-return-value__heading">Return values</h5> <h5 class="phpdocumentor-return-value__heading">Return values</h5>
<span class="phpdocumentor-signature__response_type"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a>|null</span> <span class="phpdocumentor-signature__response_type">array&lt;string|int, mixed&gt;|null</span>
&mdash; &mdash;
<section class="phpdocumentor-description"></section> <section class="phpdocumentor-description"></section>
@ -373,7 +410,7 @@ exactly the same way it was stored by <code class="prettyprint">exchangeAuthCode
<aside class="phpdocumentor-element-found-in"> <aside class="phpdocumentor-element-found-in">
<abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr> <abbr class="phpdocumentor-element-found-in__file" title="src/Storage/TokenStorageInterface.php"><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></abbr>
: :
<span class="phpdocumentor-element-found-in__line">120</span> <span class="phpdocumentor-element-found-in__line">135</span>
</aside> </aside>
<p class="phpdocumentor-summary">Revoke Access Token</p> <p class="phpdocumentor-summary">Revoke Access Token</p>

View File

@ -112,7 +112,6 @@
</ul> </ul>
<h3>T</h3> <h3>T</h3>
<ul class="phpdocumentor-list"> <ul class="phpdocumentor-list">
<li><a href="files/src-storage-token.html"><abbr title="src/Storage/Token.php">Token.php</abbr></a></li>
<li><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></li> <li><a href="files/src-storage-tokenstorageinterface.html"><abbr title="src/Storage/TokenStorageInterface.php">TokenStorageInterface.php</abbr></a></li>
</ul> </ul>
<section data-search-results class="phpdocumentor-search-results phpdocumentor-search-results--hidden"> <section data-search-results class="phpdocumentor-search-results phpdocumentor-search-results--hidden">

View File

@ -280,6 +280,16 @@ Search.appendIndex(
"name": "INVALID_SCOPE", "name": "INVALID_SCOPE",
"summary": "", "summary": "",
"url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_SCOPE" "url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_SCOPE"
}, {
"fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AINVALID_GRANT",
"name": "INVALID_GRANT",
"summary": "",
"url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_GRANT"
}, {
"fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AINVALID_REQUEST",
"name": "INVALID_REQUEST",
"summary": "",
"url": "classes/Taproot-IndieAuth-IndieAuthException.html#constant_INVALID_REQUEST"
}, { }, {
"fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AEXC_INFO", "fqsen": "\\Taproot\\IndieAuth\\IndieAuthException\u003A\u003AEXC_INFO",
"name": "EXC_INFO", "name": "EXC_INFO",
@ -640,46 +650,6 @@ Search.appendIndex(
"name": "Sqlite3Storage", "name": "Sqlite3Storage",
"summary": "", "summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html" "url": "classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token",
"name": "Token",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A__construct\u0028\u0029",
"name": "__construct",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method___construct"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003AgetData\u0028\u0029",
"name": "getData",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method_getData"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003AgetKey\u0028\u0029",
"name": "getKey",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method_getKey"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A__toString\u0028\u0029",
"name": "__toString",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method___toString"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003AjsonSerialize\u0028\u0029",
"name": "jsonSerialize",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#method_jsonSerialize"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A\u0024key",
"name": "key",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#property_key"
}, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\Token\u003A\u003A\u0024data",
"name": "data",
"summary": "",
"url": "classes/Taproot-IndieAuth-Storage-Token.html#property_data"
}, { }, {
"fqsen": "\\Taproot\\IndieAuth\\Storage\\TokenStorageInterface", "fqsen": "\\Taproot\\IndieAuth\\Storage\\TokenStorageInterface",
"name": "TokenStorageInterface", "name": "TokenStorageInterface",

View File

@ -95,8 +95,6 @@
<dd>Filesystem JSON Token Storage</dd> <dd>Filesystem JSON Token Storage</dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"><abbr title="\Taproot\IndieAuth\Storage\Sqlite3Storage">Sqlite3Storage</abbr></a></dt> <dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"><abbr title="\Taproot\IndieAuth\Storage\Sqlite3Storage">Sqlite3Storage</abbr></a></dt>
<dd></dd> <dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a></dt>
<dd></dd>
</dl> </dl>

View File

@ -111,8 +111,6 @@
<dd>Filesystem JSON Token Storage</dd> <dd>Filesystem JSON Token Storage</dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"><abbr title="\Taproot\IndieAuth\Storage\Sqlite3Storage">Sqlite3Storage</abbr></a></dt> <dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Sqlite3Storage.html"><abbr title="\Taproot\IndieAuth\Storage\Sqlite3Storage">Sqlite3Storage</abbr></a></dt>
<dd></dd> <dd></dd>
<dt class="phpdocumentor-table-of-contents__entry -class"><a href="classes/Taproot-IndieAuth-Storage-Token.html"><abbr title="\Taproot\IndieAuth\Storage\Token">Token</abbr></a></dt>
<dd></dd>
</dl> </dl>

View File

@ -19,6 +19,8 @@ class IndieAuthException extends Exception {
const INVALID_STATE = 9; const INVALID_STATE = 9;
const INVALID_CODE_CHALLENGE = 10; const INVALID_CODE_CHALLENGE = 10;
const INVALID_SCOPE = 11; const INVALID_SCOPE = 11;
const INVALID_GRANT = 12;
const INVALID_REQUEST = 13;
const EXC_INFO = [ const EXC_INFO = [
self::INTERNAL_ERROR => ['statusCode' => 500, 'name' => 'Internal Server Error', 'explanation' => 'An internal server error occurred.'], self::INTERNAL_ERROR => ['statusCode' => 500, 'name' => 'Internal Server Error', 'explanation' => 'An internal server error occurred.'],
@ -34,6 +36,8 @@ class IndieAuthException extends Exception {
self::INVALID_STATE => ['statusCode' => 302, 'name' => 'Invalid state Parameter', 'error' => 'invalid_request'], self::INVALID_STATE => ['statusCode' => 302, 'name' => 'Invalid state Parameter', 'error' => 'invalid_request'],
self::INVALID_CODE_CHALLENGE => ['statusCode' => 302, 'name' => 'Invalid code_challenge Parameter', 'error' => 'invalid_request'], self::INVALID_CODE_CHALLENGE => ['statusCode' => 302, 'name' => 'Invalid code_challenge Parameter', 'error' => 'invalid_request'],
self::INVALID_SCOPE => ['statusCode' => 302, 'name' => 'Invalid scope Parameter', 'error' => 'invalid_request'], self::INVALID_SCOPE => ['statusCode' => 302, 'name' => 'Invalid scope Parameter', 'error' => 'invalid_request'],
self::INVALID_GRANT => ['statusCode' => 400, 'name' => 'The provided credentials were not valid.', 'error' => 'invalid_grant'],
self::INVALID_REQUEST => ['statusCode' => 400, 'name' => 'Invalid Request', 'error' => 'invalid_request'],
]; ];
protected ServerRequestInterface $request; protected ServerRequestInterface $request;

View File

@ -324,65 +324,63 @@ class Server {
$bodyParams = $request->getParsedBody(); $bodyParams = $request->getParsedBody();
if (!isset($bodyParams['code'])) {
$this->logger->warning('The exchange request was missing the code parameter. Returning an error response.');
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request',
'error_description' => 'The code parameter was missing.'
]));
}
// Attempt to internally exchange the provided auth code for an access token. // Attempt to internally exchange the provided auth code for an access token.
// We do this before anything else so that the auth code is invalidated as soon as the request starts, // We do this before anything else so that the auth code is invalidated as soon as the request starts,
// and the resulting access token is revoked if we encounter an error. This ends up providing a simpler // and the resulting access token is revoked if we encounter an error. This ends up providing a simpler
// and more flexible interface for TokenStorage implementors. // and more flexible interface for TokenStorage implementors.
if (array_key_exists('code', $bodyParams)) { try {
$token = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code']); // Call the token exchange method, passing in a callback which performs additional validation
// on the auth code before it gets exchanged.
$tokenData = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code'], function (array $authCode) use ($request, $bodyParams) {
// Verify that all required parameters are included.
$requiredParameters = ['client_id', 'redirect_uri', 'code_verifier'];
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]);
});
if (!empty($missingRequiredParameters)) {
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]);
throw IndieAuthException::create(IndieAuthException::INVALID_REQUEST, $request);
}
if (is_null($token)) { // Verify that it was issued for the same client_id and redirect_uri
$this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams); if ($authCode['client_id'] !== $bodyParams['client_id']
return new Response(400, ['content-type' => 'application/json'], json_encode([ || $authCode['redirect_uri'] !== $bodyParams['redirect_uri']) {
'error' => 'invalid_grant', $this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
'error_description' => 'The provided credentials were not valid.' throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
])); }
}
} // The else case is handled by the code below.
// Verify that all required parameters are included. // Check that the supplied code_verifier hashes to the stored code_challenge
$requiredParameters = ['client_id', 'redirect_uri', 'code', 'code_verifier']; // TODO: support method = plain as well as S256.
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) { if (!hash_equals($authCode['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]); $this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
}); throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
if (!empty($missingRequiredParameters)) { }
if (isset($token)) {
$this->tokenStorage->revokeAccessToken($token->getKey()); // Check that this token either grants at most the profile scope.
} $requestedScopes = explode(' ', $authCode['scope'] ?? '');
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]); if (!empty($requestedScopes) && $requestedScopes != ['profile']) {
$this->logger->error("An exchange request for a token granting scopes other than “profile” was sent to the authorization endpoint.");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
});
} catch (IndieAuthException $e) {
// If an exception was thrown, return a corresponding error response.
return new Response(400, ['content-type' => 'application/json'], json_encode([ return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request', 'error' => $e->getInfo()['error'],
'error_description' => 'The following required parameters were missing or empty: ' . join(', ', $missingRequiredParameters) 'error_description' => $e->getMessage()
])); ]));
} }
// Verify that it was issued for the same client_id and redirect_uri if (is_null($tokenData)) {
if ($token->getData()['client_id'] !== $bodyParams['client_id'] $this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams);
|| $token->getData()['redirect_uri'] !== $bodyParams['redirect_uri']) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// Check that the supplied code_verifier hashes to the stored code_challenge
// TODO: support method = plain as well as S256.
if (!hash_equals($token->getData()['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// Check that this token either grants at most the profile scope.
$requestedScopes = explode(' ', $token->getData()['scope'] ?? '');
if (!empty($requestedScopes) && $requestedScopes != ['profile']) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("An exchange request for a token granting scopes other than “profile” was sent to the authorization endpoint.");
return new Response(400, ['content-type' => 'application/json'], json_encode([ return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant', 'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.' 'error_description' => 'The provided credentials were not valid.'
@ -395,7 +393,9 @@ class Server {
return new Response(200, [ return new Response(200, [
'content-type' => 'application/json', 'content-type' => 'application/json',
'cache-control' => 'no-store', 'cache-control' => 'no-store',
], json_encode(array_filter($token->getData(), function ($k) { ], json_encode(array_filter($tokenData, function ($k) {
// Prevent codes exchanged at the authorization endpoint from returning any information other than
// me and profile.
return in_array($k, ['me', 'profile']); return in_array($k, ['me', 'profile']);
}, ARRAY_FILTER_USE_KEY))); }, ARRAY_FILTER_USE_KEY)));
} }
@ -586,7 +586,7 @@ class Server {
// Return a redirect to the client app. // Return a redirect to the client app.
return new Response(302, [ return new Response(302, [
'Location' => appendQueryParams($queryParams['redirect_uri'], [ 'Location' => appendQueryParams($queryParams['redirect_uri'], [
'code' => $authCode->getKey(), 'code' => $authCode,
'state' => $code['state'] 'state' => $code['state']
]), ]),
'Cache-control' => 'no-cache' 'Cache-control' => 'no-cache'
@ -679,79 +679,81 @@ class Server {
$bodyParams = $request->getParsedBody(); $bodyParams = $request->getParsedBody();
if (!isset($bodyParams['code'])) {
$this->logger->warning('The exchange request was missing the code parameter. Returning an error response.');
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request',
'error_description' => 'The code parameter was missing.'
]));
}
// Attempt to internally exchange the provided auth code for an access token. // Attempt to internally exchange the provided auth code for an access token.
// We do this before anything else so that the auth code is invalidated as soon as the request starts, // We do this before anything else so that the auth code is invalidated as soon as the request starts,
// and the resulting access token is revoked if we encounter an error. This ends up providing a simpler // and the resulting access token is revoked if we encounter an error. This ends up providing a simpler
// and more flexible interface for TokenStorage implementors. // and more flexible interface for TokenStorage implementors.
if (array_key_exists('code', $bodyParams)) { try {
$token = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code']); // Call the token exchange method, passing in a callback which performs additional validation
// on the auth code before it gets exchanged.
$tokenData = $this->tokenStorage->exchangeAuthCodeForAccessToken($bodyParams['code'], function (array $authCode) use ($request, $bodyParams) {
// Verify that all required parameters are included.
$requiredParameters = ['client_id', 'redirect_uri', 'code_verifier'];
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]);
});
if (!empty($missingRequiredParameters)) {
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]);
throw IndieAuthException::create(IndieAuthException::INVALID_REQUEST, $request);
}
if (is_null($token)) { // Verify that it was issued for the same client_id and redirect_uri
$this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams); if ($authCode['client_id'] !== $bodyParams['client_id']
return new Response(400, ['content-type' => 'application/json'], json_encode([ || $authCode['redirect_uri'] !== $bodyParams['redirect_uri']) {
'error' => 'invalid_grant', $this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
'error_description' => 'The provided credentials were not valid.' throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
])); }
}
}
// Verify that all required parameters are included. // Check that the supplied code_verifier hashes to the stored code_challenge
$requiredParameters = ['client_id', 'redirect_uri', 'code', 'code_verifier']; // TODO: support method = plain as well as S256.
$missingRequiredParameters = array_filter($requiredParameters, function ($p) use ($bodyParams) { if (!hash_equals($authCode['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
return !array_key_exists($p, $bodyParams) || empty($bodyParams[$p]); $this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
}); throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
if (!empty($missingRequiredParameters)) { }
if (isset($token)) {
$this->tokenStorage->revokeAccessToken($token->getKey()); // Check that scope is not empty.
} if (empty($authCode['scope'])) {
$this->logger->warning('The exchange request was missing required parameters. Returning an error response.', ['missing' => $missingRequiredParameters]); $this->logger->error("An exchange request for a token with an empty scope was sent to the token endpoint.");
throw IndieAuthException::create(IndieAuthException::INVALID_GRANT, $request);
}
});
} catch (IndieAuthException $e) {
// If an exception was thrown, return a corresponding error response.
return new Response(400, ['content-type' => 'application/json'], json_encode([ return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_request', 'error' => $e->getInfo()['error'],
'error_description' => 'The following required parameters were missing or empty: ' . join(', ', $missingRequiredParameters) 'error_description' => $e->getMessage()
])); ]));
} }
// Verify that it was issued for the same client_id and redirect_uri if (is_null($tokenData)) {
if ($token->getData()['client_id'] !== $bodyParams['client_id'] $this->logger->error('Attempting to exchange an auth code for a token resulted in null.', $bodyParams);
|| $token->getData()['redirect_uri'] !== $bodyParams['redirect_uri']) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided client_id and/or redirect_uri did not match those stored in the token.");
return new Response(400, ['content-type' => 'application/json'], json_encode([ return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant', 'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.' 'error_description' => 'The provided credentials were not valid.'
])); ]));
} }
// Check that the supplied code_verifier hashes to the stored code_challenge // TODO: return an error if the token doesnt contain a me key.
// TODO: support method = plain as well as S256.
if (!hash_equals($token->getData()['code_challenge'], generatePKCECodeChallenge($bodyParams['code_verifier']))) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("The provided code_verifier did not hash to the stored code_challenge");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// If the auth code was issued with no scope, return an error. // If everything checked out, return {"me": "https://example.com"} response
if (empty($token->getData()['scope'])) {
$this->tokenStorage->revokeAccessToken($token->getKey());
$this->logger->error("Cannot issue an access token with no scopes.");
return new Response(400, ['content-type' => 'application/json'], json_encode([
'error' => 'invalid_grant',
'error_description' => 'The provided credentials were not valid.'
]));
}
// If everything checks out, generate an access token and return it.
return new Response(200, [ return new Response(200, [
'content-type' => 'application/json', 'content-type' => 'application/json',
'cache-control' => 'no-store' 'cache-control' => 'no-store',
], json_encode(array_merge([ ], json_encode(array_merge([
'access_token' => $token->getKey(), // Ensure that the token_type key is present, if tokenStorage doesnt include it.
'token_type' => 'Bearer' 'token_type' => 'Bearer'
], array_filter($token->getData(), function ($k) { ], array_filter($tokenData, function ($k) {
return in_array($k, ['me', 'profile', 'scope']); // We should be able to trust the return data from tokenStorage, but theres no harm in
// preventing code_challenges from leaking, per OAuth2.
return !in_array($k, ['code_challenge', 'code_challenge_method']);
}, ARRAY_FILTER_USE_KEY)))); }, ARRAY_FILTER_USE_KEY))));
} }

View File

@ -7,6 +7,7 @@ use Exception;
use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger; use Psr\Log\NullLogger;
use Taproot\IndieAuth\IndieAuthException;
use function Taproot\IndieAuth\generateRandomString; use function Taproot\IndieAuth\generateRandomString;
@ -64,7 +65,7 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
// TokenStorageInterface Methods. // TokenStorageInterface Methods.
public function createAuthCode(array $data): ?Token { public function createAuthCode(array $data): ?string {
$authCode = generateRandomString(self::TOKEN_LENGTH); $authCode = generateRandomString(self::TOKEN_LENGTH);
$accessToken = $this->hash($authCode); $accessToken = $this->hash($authCode);
@ -75,17 +76,17 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
if (!$this->put($accessToken, $data)) { if (!$this->put($accessToken, $data)) {
return null; return null;
} }
return new Token($authCode, $data); return $authCode;
} }
public function exchangeAuthCodeForAccessToken(string $code): ?Token { public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array {
// Hash the auth code to get the theoretical matching access token filename. // Hash the auth code to get the theoretical matching access token filename.
$accessToken = $this->hash($code); $accessToken = $this->hash($code);
// Prevent the token file from being read, modified or deleted while were working with it. // Prevent the token file from being read, modified or deleted while were working with it.
// r+ to allow reading and writing, but to make sure we dont create the file if it doesnt // r+ to allow reading and writing, but to make sure we dont create the file if it doesnt
// already exist. // already exist.
return $this->withLock($this->getPath($accessToken), 'r+', function ($fp) use ($accessToken) { return $this->withLock($this->getPath($accessToken), 'r+', function ($fp) use ($accessToken, $validateAuthCode) {
// Read the file contents. // Read the file contents.
$fileContents = ''; $fileContents = '';
while ($d = fread($fp, 1024)) { $fileContents .= $d; } while ($d = fread($fp, 1024)) { $fileContents .= $d; }
@ -100,6 +101,18 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
// Make sure the auth code isnt expired. // Make sure the auth code isnt expired.
if (($data['valid_until'] ?? 0) < time()) { return null; } if (($data['valid_until'] ?? 0) < time()) { return null; }
// The auth code is valid as far as we know, pass it to the validation callback passed from the
// Server.
try {
$validateAuthCode($data);
} catch (IndieAuthException $e) {
// If there was an issue with the auth code, delete it before bubbling the exception
// up to the Server for handling. We currently have a lock on the file path, so pass
// false to $observeLock to prevent a deadlock.
$this->delete($accessToken, false);
throw $e;
}
// If the access token is valid, mark it as redeemed and set a new expiry time. // If the access token is valid, mark it as redeemed and set a new expiry time.
$data['exchanged_at'] = time(); $data['exchanged_at'] = time();
@ -120,11 +133,18 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
if (fwrite($fp, $jsonData) === false) { return null; } if (fwrite($fp, $jsonData) === false) { return null; }
if (ftruncate($fp, strlen($jsonData)) === false) { return null; } if (ftruncate($fp, strlen($jsonData)) === false) { return null; }
return new Token($accessToken, $data); // Return the OAuth2-compatible access token data to the Server for passing onto
// the client app. Passed via array_filter to remove the scope key if scope is null.
return array_filter([
'access_token' => $accessToken,
'scope' => $data['scope'] ?? null,
'me' => $data['me'],
'profile' => $data['profile'] ?? null
]);
}); });
} }
public function getAccessToken(string $token): ?Token { public function getAccessToken(string $token): ?array {
$data = $this->get($token); $data = $this->get($token);
if (!is_array($data)) { return null; } if (!is_array($data)) { return null; }
@ -137,7 +157,7 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
if (is_int($data['valid_until']) && $data['valid_until'] < time()) { return null; } if (is_int($data['valid_until']) && $data['valid_until'] < time()) { return null; }
// The token is valid! // The token is valid!
return new Token($token, $data); return $data;
} }
public function revokeAccessToken(string $token): bool { public function revokeAccessToken(string $token): bool {
@ -205,12 +225,16 @@ class FilesystemJsonStorage implements TokenStorageInterface, LoggerAwareInterfa
}); });
} }
public function delete(string $key): bool { public function delete(string $key, $observeLock=true): bool {
$path = $this->getPath($key); $path = $this->getPath($key);
if (file_exists($path)) { if (file_exists($path)) {
return $this->withLock($path, 'r', function ($fp) use ($path) { if ($observeLock) {
return $this->withLock($path, 'r', function ($fp) use ($path) {
return unlink($path);
});
} else {
return unlink($path); return unlink($path);
}); }
} }
return false; return false;
} }

View File

@ -1,31 +0,0 @@
<?php declare(strict_types=1);
namespace Taproot\IndieAuth\Storage;
use JsonSerializable;
class Token implements JsonSerializable {
protected string $key;
protected array $data;
public function __construct(string $key, array $data) {
$this->key = $key;
$this->data = $data;
}
public function getData(): array {
return $this->data;
}
public function getKey(): string {
return $this->key;
}
public function __toString() {
return $this->key;
}
public function jsonSerialize() {
return $this->data;
}
}

View File

@ -61,9 +61,8 @@ interface TokenStorageInterface {
* the consent screen. Other implementations of `AuthorizationFormInterface` may add additional * the consent screen. Other implementations of `AuthorizationFormInterface` may add additional
* data, such as custom token-specific settings, or a custom token lifetime. * data, such as custom token-specific settings, or a custom token lifetime.
* *
* This method should store the data passed to it, generate a corresponding authorization code, * This method should store the data passed to it, generate a corresponding authorization code
* and return an instance of `Storage\Token` containing both. Implementations will usually add * string, and return it.
* an expiry time, usually under the `valid_until` key.
* *
* The method call and data is structured such that implementations have a lot of flexibility * The method call and data is structured such that implementations have a lot of flexibility
* about how to store authorization code data. It could be a record in an auth code database * about how to store authorization code data. It could be a record in an auth code database
@ -76,40 +75,56 @@ interface TokenStorageInterface {
* recommended to log it internally for reference. For the same reason, this method should not * recommended to log it internally for reference. For the same reason, this method should not
* throw exceptions. * throw exceptions.
*/ */
public function createAuthCode(array $data): ?Token; public function createAuthCode(array $data): ?string;
/** /**
* Exchange Authorization Code for Access Token * Exchange Authorization Code for Access Token
* *
* Attempt to exchange an authorization code identified by `$code` for * Attempt to exchange an authorization code identified by `$code` for
* an access token, returning it in a `Token` on success and null on error. * an access token. Return an array of access token data to be passed onto
* the client app on success, and null on error.
* *
* This method is called at the beginning of a code exchange request, before * This method is called at the beginning of a code exchange request, before
* further error checking or validation is applied. On an error, the created * further error checking or validation is applied. It should proceed as
* access token is immediately revoked via `revokeAccessToken()`. * follows.
* *
* For this reason, the token data in the returned Token object MUST include * * Attempt to fetch the authorization code data identified by $code. If
* the `client_id` and `redirect_uri` parameters associated with the * it does not exist or has expired, return null;
* authorization code, as these are used by the IndieAuth Server for further * * Pass the authorization code data array to $validateAuthCode for validation.
* validation. * If there is a problem with the code, a `Taproot\IndieAuth\IndieAuthException`
* will be thrown. This method should catch it, invalidate the authorization
* code data, then re-throw the exception for handling by Server.
* * If the authorization code data passed all checks, convert it into an access
* token, invalidate the auth code to prevent re-use, and store the access token
* data internally.
* * Return an array of access token data to be passed onto the client app. It MUST
* contain the following keys:
* * `me`
* * `access_token`
* Additonally, it SHOULD contain the following keys:
* * `scope`, if the token grants any scope
* And MAY contain additional keys, such as:
* * `profile`
* * `expires_at`
* *
* This method is responsible for ensuring that the matched auth code is * If the authorization code was redeemed at the authorization endpoint, Server will
* not expired. If it is, it should return null, presumably after deleting * only pass the `me` and `profile` keys onto the client. In both cases, it will filter
* the corresponding authorization code record. * out `code_challenge` keys to prevent that data from accidentally being leaked to
* clients.
* *
* If the exchanged access token should expire, this method should set its * @param string $code The Authorization Code to attempt to exchange.
* expiry time, usually in a `valid_until` key. * @param callable $validateAuthCode A callable to perform additional validation if valid auth code data is found. Takes `array $authCodeData`, raises `Taproot\IndieAuth\IndieAuthException` on invalid data, which should be bubbled up to the caller after any clean-up. Returns void.
* @return array|null An array of access token data to return to the client on success, null on any error.
*/ */
public function exchangeAuthCodeForAccessToken(string $code): ?Token; public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array;
/** /**
* Get Access Token * Get Access Token
* *
* Fetch access token data identified by the token `$token`, returning * Fetch access token data identified by the token `$token`, returning
* null if it is expired or invalid. The data should be structured in * null if it is expired or invalid.
* exactly the same way it was stored by `exchangeAuthCodeForAccessToken`.
*/ */
public function getAccessToken(string $token): ?Token; public function getAccessToken(string $token): ?array;
/** /**
* Revoke Access Token * Revoke Access Token

View File

@ -7,7 +7,6 @@ use PHPUnit\Framework\TestCase;
use Taproot\IndieAuth\Storage\FilesystemJsonStorage; use Taproot\IndieAuth\Storage\FilesystemJsonStorage;
const SECRET = '1111111111111111111111111111111111111111111111111111111111111111'; const SECRET = '1111111111111111111111111111111111111111111111111111111111111111';
const TMP_DIR = __DIR__ . '/tmp';
class FilesystemJsonStorageTest extends TestCase { class FilesystemJsonStorageTest extends TestCase {
protected function setUp(): void { protected function setUp(): void {

View File

@ -613,20 +613,21 @@ EOT
] as $endpointHandler) { ] as $endpointHandler) {
// Create an auth code. // Create an auth code.
$codeVerifier = generateRandomString(32); $codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([ $authCodeData = [
'client_id' => 'https://client.example.com/', 'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth', 'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier), 'code_challenge' => generatePKCECodeChallenge($codeVerifier),
'state' => '12345', 'state' => '12345',
'code_challenge_method' => 'S256' 'code_challenge_method' => 'S256'
]); ];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params. // Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody(array_merge([ $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody(array_merge([
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'code' => $authCode->getKey(), 'code' => $authCode,
'client_id' => $authCode->getData()['client_id'], 'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'], 'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier 'code_verifier' => $codeVerifier
], $params)); ], $params));
@ -644,21 +645,22 @@ EOT
// Create an auth code. // Create an auth code.
$codeVerifier = generateRandomString(32); $codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([ $authCodeData = [
'client_id' => 'https://client.example.com/', 'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth', 'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier), 'code_challenge' => generatePKCECodeChallenge($codeVerifier),
'state' => '12345', 'state' => '12345',
'code_challenge_method' => 'S256', 'code_challenge_method' => 'S256',
'scope' => 'create update' 'scope' => 'create update'
]); ];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params. // Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([ $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'code' => $authCode->getKey(), 'code' => $authCode,
'client_id' => $authCode->getData()['client_id'], 'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'], 'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier 'code_verifier' => $codeVerifier
]); ]);
@ -675,7 +677,7 @@ EOT
// Create an auth code. // Create an auth code.
$codeVerifier = generateRandomString(32); $codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([ $authCodeData = [
'client_id' => 'https://client.example.com/', 'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth', 'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier), 'code_challenge' => generatePKCECodeChallenge($codeVerifier),
@ -686,14 +688,15 @@ EOT
'profile' => [ 'profile' => [
'name' => 'Me' 'name' => 'Me'
] ]
]); ];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params. // Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([ $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'code' => $authCode->getKey(), 'code' => $authCode,
'client_id' => $authCode->getData()['client_id'], 'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'], 'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier 'code_verifier' => $codeVerifier
]); ]);
@ -732,20 +735,22 @@ EOT
// Create an auth code. // Create an auth code.
$codeVerifier = generateRandomString(32); $codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([ $authCodeData = [
'client_id' => 'https://client.example.com/', 'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth', 'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier), 'code_challenge' => generatePKCECodeChallenge($codeVerifier),
'state' => '12345', 'state' => '12345',
'code_challenge_method' => 'S256' 'code_challenge_method' => 'S256',
]); 'me' => 'https://me.example.com'
];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params. // Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([ $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'code' => $authCode->getKey(), 'code' => $authCode,
'client_id' => $authCode->getData()['client_id'], 'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'], 'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier 'code_verifier' => $codeVerifier
]); ]);
@ -762,7 +767,7 @@ EOT
// Create an auth code. // Create an auth code.
$codeVerifier = generateRandomString(32); $codeVerifier = generateRandomString(32);
$authCode = $storage->createAuthCode([ $authCodeData = [
'client_id' => 'https://client.example.com/', 'client_id' => 'https://client.example.com/',
'redirect_uri' => 'https://client.example.com/auth', 'redirect_uri' => 'https://client.example.com/auth',
'code_challenge' => generatePKCECodeChallenge($codeVerifier), 'code_challenge' => generatePKCECodeChallenge($codeVerifier),
@ -773,14 +778,15 @@ EOT
'profile' => [ 'profile' => [
'name' => 'Me' 'name' => 'Me'
] ]
]); ];
$authCode = $storage->createAuthCode($authCodeData);
// Create what would by default be a successful request, then merge specific error-inducing params. // Create what would by default be a successful request, then merge specific error-inducing params.
$req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([ $req = (new ServerRequest('POST', 'https://example.com'))->withParsedBody([
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'code' => $authCode->getKey(), 'code' => $authCode,
'client_id' => $authCode->getData()['client_id'], 'client_id' => $authCodeData['client_id'],
'redirect_uri' => $authCode->getData()['redirect_uri'], 'redirect_uri' => $authCodeData['redirect_uri'],
'code_verifier' => $codeVerifier 'code_verifier' => $codeVerifier
]); ]);
@ -789,11 +795,11 @@ EOT
$this->assertEquals(200, $res->getStatusCode()); $this->assertEquals(200, $res->getStatusCode());
$this->assertEquals('no-store', $res->getHeaderLine('cache-control')); $this->assertEquals('no-store', $res->getHeaderLine('cache-control'));
$resJson = json_decode((string) $res->getBody(), true); $resJson = json_decode((string) $res->getBody(), true);
$this->assertEquals(hash_hmac('sha256', $authCode->getKey(), SERVER_SECRET), $resJson['access_token']); $this->assertEquals(hash_hmac('sha256', $authCode, SERVER_SECRET), $resJson['access_token']);
$this->assertEquals('Bearer', $resJson['token_type']); $this->assertEquals('Bearer', $resJson['token_type']);
$this->assertEquals($authCode->getData()['me'], $resJson['me']); $this->assertEquals($authCodeData['me'], $resJson['me']);
$this->assertEquals($authCode->getData()['profile'], $resJson['profile']); $this->assertEquals($authCodeData['profile'], $resJson['profile']);
$this->assertTrue(scopeEquals($authCode->getData()['scope'], $resJson['scope'])); $this->assertTrue(scopeEquals($authCodeData['scope'], $resJson['scope']));
} }
/** /**
@ -832,15 +838,15 @@ function scopeEquals($scope1, $scope2): bool {
} }
class NullTokenStorage implements TokenStorageInterface { class NullTokenStorage implements TokenStorageInterface {
public function createAuthCode(array $data): ?Token { public function createAuthCode(array $data): ?string {
return null; return null;
} }
public function getAccessToken(string $token): ?Token { public function getAccessToken(string $token): ?array {
return null; return null;
} }
public function exchangeAuthCodeForAccessToken(string $code): ?Token { public function exchangeAuthCodeForAccessToken(string $code, callable $validateAuthCode): ?array {
return null; return null;
} }