Repository: badgateway/oauth2-client
Pull request #153 (2024-08-22 05:17:16)

Throw error when we get an invalid reply back from a token endpoint.
See #151.



Patch:
diff --git a/changelog.md b/changelog.md
index 03b9a41..69ea0cc 100644
--- a/changelog.md
+++ b/changelog.md
@@ -6,6 +6,7 @@ Changelog
 
 * #151: Add 'Accept' header on token requests to fix a Github compatibility
   issue.
+* #151: Throw error when we get an invalid reply from a token endpoint.
 
 
 2.4.0 (2024-07-27)
diff --git a/src/client.ts b/src/client.ts
index 785c4b8..b035e65 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -448,15 +448,20 @@ export class OAuth2Client {
   /**
    * Converts the JSON response body from the token endpoint to an OAuth2Token type.
    */
-  tokenResponseToOAuth2Token(resp: Promise<TokenResponse>): Promise<OAuth2Token> {
-
-    return resp.then(body => {
-      return {
-        accessToken: body.access_token,
-        expiresAt: body.expires_in ? Date.now() + (body.expires_in * 1000) : null,
-        refreshToken: body.refresh_token ?? null,
-      };
-    });
+  async tokenResponseToOAuth2Token(resp: Promise<TokenResponse>): Promise<OAuth2Token> {
+
+    const body = await resp;
+
+    if (!body?.access_token) {
+      console.warn('Invalid OAuth2 Token Response: ', body);
+      throw new TypeError('We received an invalid token response from an OAuth2 server.');
+    }
+
+    return {
+      accessToken: body.access_token,
+      expiresAt: body.expires_in ? Date.now() + (body.expires_in * 1000) : null,
+      refreshToken: body.refresh_token ?? null,
+    };
 
   }
 
diff --git a/src/messages.ts b/src/messages.ts
index 30ecd65..f05e955 100644
--- a/src/messages.ts
+++ b/src/messages.ts
@@ -70,7 +70,7 @@ export type AuthorizationCodeRequest = {
 export type TokenResponse = {
   access_token: string;
   token_type: string;
-  expires_in: number;
+  expires_in?: number;
   refresh_token?: string;
   scope?: string;
 }


Test patch:
diff --git a/test/client.ts b/test/client.ts
new file mode 100644
index 0000000..6f918b5
--- /dev/null
+++ b/test/client.ts
@@ -0,0 +1,62 @@
+import { expect } from 'chai';
+
+import { OAuth2Client } from '../src';
+
+
+describe('tokenResponseToOAuth2Token', () => {
+
+  it('should convert a JSON response to a OAuth2Token', async () => {
+
+    const client = new OAuth2Client({
+      clientId: 'foo',
+    });
+    const token = await client.tokenResponseToOAuth2Token(Promise.resolve({
+      token_type: 'bearer',
+      access_token: 'foo-bar',
+    }));
+
+    expect(token).to.deep.equal({
+      accessToken: 'foo-bar',
+      expiresAt: null,
+      refreshToken: null,
+    });
+
+  });
+
+  it('should error when an invalid JSON object is passed', async () => {
+
+    const client = new OAuth2Client({
+      clientId: 'foo',
+    });
+
+    let caught = false;
+    try {
+      await client.tokenResponseToOAuth2Token(Promise.resolve({
+        funzies: 'foo-bar',
+      } as any));
+    } catch (err) {
+      expect(err).to.be.an.instanceof(TypeError);
+      caught = true;
+    }
+
+    expect(caught).to.equal(true);
+
+  });
+  it('should error when an empty body is passed', async () => {
+
+    const client = new OAuth2Client({
+      clientId: 'foo',
+    });
+
+    let caught = false;
+    try {
+      await client.tokenResponseToOAuth2Token(Promise.resolve(undefined as any));
+    } catch (err) {
+      expect(err).to.be.an.instanceof(TypeError);
+      caught = true;
+    }
+
+    expect(caught).to.equal(true);
+
+  });
+});