From 0996d2bc3a22b6cbaddf62dd779f9c3effcbba7a Mon Sep 17 00:00:00 2001 From: advplyr <advplyr@users.noreply.github.com> Date: Sat, 30 Dec 2023 16:38:15 +0000 Subject: [PATCH] deploy: bc5e047dcc679da2cdb842fe71a6a5f43eeffba0 --- index.html | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 309 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 222309e..154c3f1 100644 --- a/index.html +++ b/index.html @@ -327,6 +327,15 @@ <li> <a href="#logout" class="toc-h2 toc-link" data-title="Logout">Logout</a> </li> + <li> + <a href="#oauth2-authorization-request" class="toc-h2 toc-link" data-title="OAuth2 Authorization Request">OAuth2 Authorization Request</a> + </li> + <li> + <a href="#oauth2-callback" class="toc-h2 toc-link" data-title="OAuth2 Callback">OAuth2 Callback</a> + </li> + <li> + <a href="#oauth2-mobile-redirect" class="toc-h2 toc-link" data-title="OAuth2 Mobile Redirect">OAuth2 Mobile Redirect</a> + </li> <li> <a href="#initialize-the-server" class="toc-h2 toc-link" data-title="Initialize the Server">Initialize the Server</a> </li> @@ -1302,12 +1311,304 @@ <h3 id='response-2'>Response</h3> <td>Success</td> </tr> </tbody></table> +<h2 id='oauth2-authorization-request'>OAuth2 Authorization Request</h2><div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="s2">"https://abs.example.com/auth/openid?code_challenge=1234&code_challenge_method=S256&redirect_uri=audiobookshelf%3A%2F%2Foauth&client_id=Audiobookshelf-App&response_type=code"</span> +</code></pre></div> +<blockquote> +<p>Response header (depending on SSO provider)</p> +</blockquote> +<div class="highlight"><pre class="highlight plaintext"><code>Location: https://auth.example.com/application/o/authorize/?client_id=G9DbJqJ&scope=openid%20profile%20email&response_type=code&redirect_uri=https%3A%2F%2Fabs.example.com%2Fauth%2Fopenid%2Fmobile-redirect&state=2000&code_challenge=C424242&code_challenge_method=S256 +</code></pre></div> +<p>This endpoint starts a standard OAuth2 flow with PKCE (required - S256; see <a href="https://datatracker.ietf.org/doc/html/rfc7636#section-4.1">RFC7636</a>), to log the user in using SSO.</p> + +<p>For the <code>code_challenge</code>, you must randomly generate a minimum 32-byte string called verifier (PKCE challenge). +With the verifier, you can then generate the challenge. See the examples on the right side.</p> + +<blockquote> +<p>Code Challenge Pseudo-Code</p> +</blockquote> +<div class="highlight"><pre class="highlight plaintext"><code>code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) +</code></pre></div> +<blockquote> +<p>Code Challenge plain Javascript Code (all used functions are available in NodeJS and Browsers)</p> +</blockquote> +<div class="highlight"><pre class="highlight javascript"><code><span class="kd">function</span> <span class="nx">base64URLEncode</span><span class="p">(</span><span class="nx">arrayBuffer</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">base64String</span> <span class="o">=</span> <span class="nx">btoa</span><span class="p">(</span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCharCode</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">arrayBuffer</span><span class="p">)))</span> + <span class="k">return</span> <span class="nx">base64String</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\+</span><span class="sr">/g</span><span class="p">,</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\/</span><span class="sr">/g</span><span class="p">,</span> <span class="dl">'</span><span class="s1">_</span><span class="dl">'</span><span class="p">).</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/=+$/g</span><span class="p">,</span> <span class="dl">''</span><span class="p">)</span> +<span class="p">}</span> + +<span class="k">async</span> <span class="kd">function</span> <span class="nx">sha256</span><span class="p">(</span><span class="nx">plain</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">encoder</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextEncoder</span><span class="p">()</span> + <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">encoder</span><span class="p">.</span><span class="nx">encode</span><span class="p">(</span><span class="nx">plain</span><span class="p">)</span> + <span class="k">return</span> <span class="k">await</span> <span class="nb">window</span><span class="p">.</span><span class="nx">crypto</span><span class="p">.</span><span class="nx">subtle</span><span class="p">.</span><span class="nx">digest</span><span class="p">(</span><span class="dl">'</span><span class="s1">SHA-256</span><span class="dl">'</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> +<span class="p">}</span> + +<span class="kd">function</span> <span class="nx">generateRandomString</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">var</span> <span class="nx">array</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint32Array</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">crypto</span><span class="p">.</span><span class="nx">getRandomValues</span><span class="p">(</span><span class="nx">array</span><span class="p">)</span> + <span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">array</span><span class="p">,</span> <span class="p">(</span><span class="nx">dec</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span><span class="dl">'</span><span class="s1">0</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">dec</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">)).</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)).</span><span class="nx">join</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span> <span class="c1">// return as hex</span> +<span class="p">}</span> + +<span class="kd">const</span> <span class="nx">verifier</span> <span class="o">=</span> <span class="nx">generateRandomString</span><span class="p">()</span> +<span class="kd">const</span> <span class="nx">challenge</span> <span class="o">=</span> <span class="nx">base64URLEncode</span><span class="p">(</span><span class="k">await</span> <span class="nx">sha256</span><span class="p">(</span><span class="nx">verifier</span><span class="p">))</span> +</code></pre></div> +<p>On a valid request, it will return a 302-redirect (usually with a <code>Location:</code> header), which will point to the ABS-configured OAuth2 Provider. +You would usually have to open this redirect-URL in a Browser to present to the user. +Note that inside the redirect URL, among other parameters, there is a <code>state</code> parameter; save it for below.</p> + +<p>Note that you will have to preserve the cookies you receive in this call for using it in <code>/auth/openid/callback</code> (check if you need to set a parameter for the HTTP library you are using for that).</p> + +<p>When the user has logged in (successfully) inside the Browser, the Browser will redirect to the URL <code>redirect_uri</code> which should be a URL your website or app can open (like a universal app link). The call to the <code>redirect_uri</code> will include state and code GET parameters. Check if the state parameter is the same as above and use the <code>code</code> for the call to <code>/auth/openid/callback</code>.</p> + +<blockquote> +<p>Redirect URL which is opened in the user's browser by the SSO Provider</p> +</blockquote> +<div class="highlight"><pre class="highlight plaintext"><code>redirect_uri?code=42&state=2000 +</code></pre></div><h3 id='http-request-3'>HTTP Request</h3> +<p><code>GET http://abs.example.com/auth/openid</code></p> +<h3 id='query-parameters'>Query Parameters</h3> +<table><thead> +<tr> +<th>Parameter</th> +<th>Type</th> +<th>Default</th> +<th>Description</th> +</tr> +</thead><tbody> +<tr> +<td><code>code_challenge</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>PKCE code_challenge you generated from <code>verifier</code></td> +</tr> +<tr> +<td><code>code_challenge_method</code></td> +<td>String</td> +<td>S256</td> +<td>Must be <code>S256</code></td> +</tr> +<tr> +<td><code>response_type</code></td> +<td>String</td> +<td>code</td> +<td>Only <code>code</code> is supported</td> +</tr> +<tr> +<td><code>redirect_uri</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>URL where to redirect after a successful login. Must be whitelisted in ABS</td> +</tr> +<tr> +<td><code>client_id</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>The name of your app (currently not used, but might be required at some point)</td> +</tr> +</tbody></table> + +<p>Other parameters are ignored.</p> +<h3 id='response-3'>Response</h3> +<table><thead> +<tr> +<th>Status</th> +<th>Meaning</th> +<th>Description</th> +</tr> +</thead><tbody> +<tr> +<td>302</td> +<td>REDIRECT</td> +<td>Success, save the state-parameter and follow the redirect</td> +</tr> +<tr> +<td>400</td> +<td>Bad Request</td> +<td>You submitted an invalid parameter</td> +</tr> +<tr> +<td>500</td> +<td>Internal Server Error</td> +<td>Error in the flow</td> +</tr> +</tbody></table> +<h2 id='oauth2-callback'>OAuth2 Callback</h2><div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="s2">"https://abs.example.com/auth/openid/callback?state=2000&code=42&code_verifier=1234"</span> +</code></pre></div> +<blockquote> +<p>The above command returns a JSON structured like this:</p> +</blockquote> +<div class="highlight"><pre class="highlight json tab-json"><code><span class="p">{</span><span class="w"> + </span><span class="nl">"userDefaultLibraryId"</span><span class="p">:</span><span class="s2">"b2bab335-d9aa-4141-8394-fd98767504d7"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"serverSettings"</span><span class="p">:{</span><span class="w"> + </span><span class="nl">"scannerFindCovers"</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="w"> + </span><span class="nl">"metadataFileFormat"</span><span class="p">:</span><span class="s2">"json"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"backupSchedule"</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="w"> + </span><span class="nl">"authOpenIDJwksURL"</span><span class="p">:</span><span class="s2">"https://auth.example.com/application/o/audiobookshelf/jwks/"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"authOpenIDAutoLaunch"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> + </span><span class="s2">"…"</span><span class="w"> + </span><span class="p">},</span><span class="w"> + </span><span class="nl">"Source"</span><span class="p">:</span><span class="s2">"docker"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"user"</span><span class="p">:{</span><span class="w"> + </span><span class="nl">"oldUserId"</span><span class="p">:</span><span class="s2">"usr_1234lasdnlk"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"itemTagsSelected"</span><span class="p">:[],</span><span class="w"> + </span><span class="nl">"createdAt"</span><span class="p">:</span><span class="mi">1672769098296</span><span class="p">,</span><span class="w"> + </span><span class="nl">"librariesAccessible"</span><span class="p">:[</span><span class="w"> </span><span class="p">],</span><span class="w"> + </span><span class="nl">"mediaProgress"</span><span class="p">:[],</span><span class="w"> + </span><span class="nl">"oldUserId"</span><span class="p">:</span><span class="s2">"usr_1234lasdnlk"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"permissions"</span><span class="p">:{</span><span class="w"> + </span><span class="nl">"accessExplicitContent"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> + </span><span class="nl">"delete"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> + </span><span class="nl">"download"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> + </span><span class="nl">"upload"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> + </span><span class="nl">"accessAllLibraries"</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="w"> + </span><span class="s2">"…"</span><span class="w"> + </span><span class="p">},</span><span class="w"> + </span><span class="nl">"seriesHideFromContinueListening"</span><span class="p">:[],</span><span class="w"> + </span><span class="nl">"token"</span><span class="p">:</span><span class="s2">"eyJhbGciOiJIUzI1NiIsInASDLKAMSDklmad.ASLDKlkma.PNKNASDPNdnknsdfoP"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"type"</span><span class="p">:</span><span class="s2">"admin"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"username"</span><span class="p">:</span><span class="s2">"example"</span><span class="w"> + </span><span class="p">},</span><span class="w"> + </span><span class="nl">"ereaderDevices"</span><span class="p">:[]</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></code></pre></div> +<p>This API call finalizes the OAuth2 flow. The behavior of this call depends on whether a <code>redirect_uri</code> was provided in the <code>/auth/openid</code> request:</p> + +<ul> +<li>If a <code>redirect_uri</code> was provided, this call returns user JSON data.</li> +<li>If a <code>redirect_uri</code> was not provided, it will redirect to <code>/login</code> (used internally by ABS-web).</li> +</ul> + +<p>It's important to note that the call to <code>/auth/openid/callback</code> is stateful. This means that the cookies set during the <code>/auth/openid</code> call must be sent along with this request.</p> +<h3 id='query-parameters-2'>Query Parameters</h3> +<table><thead> +<tr> +<th>Parameter</th> +<th>Type</th> +<th>Default</th> +<th>Description</th> +</tr> +</thead><tbody> +<tr> +<td><code>state</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>The state string you received when <code>redirect_uri</code> was called</td> +</tr> +<tr> +<td><code>code</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>The code you received when <code>redirect_uri</code> was called</td> +</tr> +<tr> +<td><code>code_verifier</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>This is the verifier you generated when providing the <code>code_challenge</code> in the first request</td> +</tr> +</tbody></table> + +<p>Other parameters are ignored.</p> +<h3 id='response-4'>Response</h3> +<table><thead> +<tr> +<th>Status</th> +<th>Meaning</th> +<th>Description</th> +</tr> +</thead><tbody> +<tr> +<td>200</td> +<td>OK</td> +<td>Success, user data in payload</td> +</tr> +<tr> +<td>302</td> +<td>FOUND</td> +<td>Success, redirect to /login (internal use)</td> +</tr> +<tr> +<td>400</td> +<td>Bad Request</td> +<td>You have no session</td> +</tr> +<tr> +<td>401</td> +<td>Unauthorized</td> +<td>Error from the SSO provider</td> +</tr> +<tr> +<td>500</td> +<td>Internal Server Error</td> +<td>Error in the flow</td> +</tr> +</tbody></table> + +<p>Error details are provided in the response body and in ABS logs.</p> +<h2 id='oauth2-mobile-redirect'>OAuth2 Mobile Redirect</h2><div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="s2">"https://abs.example.com/auth/openid/mobile-redirect"</span> +</code></pre></div> +<p>This is an internal endpoint, which should <strong>not</strong> be called directly by an application. It is intended purely for the redirection of SSO providers.</p> + +<p>When you provide a <code>redirect_uri</code> in <code>/auth/openid</code>, ABS performs an important operation: it saves your <code>redirect_uri</code> and instructs the SSO provider to use <code>/auth/openid/mobile-redirect</code> instead. This endpoint, in turn, redirects to your original <code>redirect_uri</code>.</p> + +<p>This mechanism allows ABS to provide a <code>https</code> callback URL, which is particularly useful for mobile apps.</p> + +<p>This call does not require a cookie or session. It matches the <code>redirect_uri</code> using the <code>state</code> parameter.</p> +<h3 id='http-request-4'>HTTP Request</h3> +<p><code>GET http://abs.example.com/auth/openid/mobile-redirect</code></p> +<h3 id='query-parameters-3'>Query Parameters</h3> +<table><thead> +<tr> +<th>Parameter</th> +<th>Type</th> +<th>Default</th> +<th>Description</th> +</tr> +</thead><tbody> +<tr> +<td><code>code</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>OAuth2 state</td> +</tr> +<tr> +<td><code>state</code></td> +<td>String</td> +<td><strong>Required</strong></td> +<td>OAuth2 code</td> +</tr> +</tbody></table> + +<p>Other parameters are ignored.</p> +<h3 id='response-5'>Response</h3> +<table><thead> +<tr> +<th>Status</th> +<th>Meaning</th> +<th>Description</th> +</tr> +</thead><tbody> +<tr> +<td>302</td> +<td>REDIRECT</td> +<td>Success</td> +</tr> +<tr> +<td>400</td> +<td>Bad Request</td> +<td>No state or no redirect_uri associated to state</td> +</tr> +<tr> +<td>500</td> +<td>Internal Server Error</td> +<td>Error in the flow</td> +</tr> +</tbody></table> <h2 id='initialize-the-server'>Initialize the Server</h2><div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="nt">-X</span> POST <span class="s2">"https://abs.example.com/init"</span> <span class="se">\</span> <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span> <span class="nt">-d</span> <span class="s1">'{"newRoot": {"username": "root", "password": "*****"}}'</span> </code></pre></div> <p>This endpoint initializes a server for use with a root user. This is required for new servers without a root user yet.</p> -<h3 id='http-request-3'>HTTP Request</h3> +<h3 id='http-request-5'>HTTP Request</h3> <p><code>POST http://abs.example.com/init</code></p> <h3 id='parameters-2'>Parameters</h3> <table><thead> @@ -1342,7 +1643,7 @@ <h4 id='new-root-user-parameters'>New Root User Parameters</h4> <td>The password of the new root user, may be empty.</td> </tr> </tbody></table> -<h3 id='response-3'>Response</h3> +<h3 id='response-6'>Response</h3> <table><thead> <tr> <th>Status</th> @@ -1372,9 +1673,9 @@ <h2 id='check-the-server-39-s-status'>Check the Server's Status</h2><div cla </span><span class="p">}</span><span class="w"> </span></code></pre></div> <p>This endpoint reports the server's initialization status.</p> -<h3 id='http-request-4'>HTTP Request</h3> +<h3 id='http-request-6'>HTTP Request</h3> <p><code>GET http://abs.example.com/status</code></p> -<h3 id='response-4'>Response</h3> +<h3 id='response-7'>Response</h3> <table><thead> <tr> <th>Status</th> @@ -1429,9 +1730,9 @@ <h2 id='ping-the-server'>Ping the Server</h2><div class="highlight"><pre class=" </span><span class="p">}</span><span class="w"> </span></code></pre></div> <p>This endpoint is a simple check to see if the server is operating and responding with JSON correctly.</p> -<h3 id='http-request-5'>HTTP Request</h3> +<h3 id='http-request-7'>HTTP Request</h3> <p><code>GET http://abs.example.com/ping</code></p> -<h3 id='response-5'>Response</h3> +<h3 id='response-8'>Response</h3> <table><thead> <tr> <th>Status</th> @@ -1464,9 +1765,9 @@ <h4 id='response-schema-3'>Response Schema</h4> <h2 id='healthcheck'>Healthcheck</h2><div class="highlight"><pre class="highlight shell tab-shell"><code>curl <span class="s2">"https://abs.example.com/healthcheck"</span> </code></pre></div> <p>This endpoint is a simple check to see if the server is operating and can respond.</p> -<h3 id='http-request-6'>HTTP Request</h3> +<h3 id='http-request-8'>HTTP Request</h3> <p><code>GET http://abs.example.com/healthcheck</code></p> -<h3 id='response-6'>Response</h3> +<h3 id='response-9'>Response</h3> <table><thead> <tr> <th>Status</th>