Skip to content

Commit

Permalink
fix(next-drupal): allow clientId/Secret in getAccessToken() params
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnAlbin committed Feb 27, 2024
1 parent fda9709 commit 5581933
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 45 deletions.
81 changes: 51 additions & 30 deletions packages/next-drupal/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
import type {
AccessToken,
BaseUrl,
DrupalClientAuth,
DrupalClientAuthAccessToken,
DrupalClientAuthClientIdSecret,
DrupalClientAuthUsernamePassword,
Expand Down Expand Up @@ -53,25 +54,28 @@ const DEFAULT_HEADERS = {
}

function isBasicAuth(
auth: DrupalClientOptions["auth"]
auth: DrupalClientAuth
): auth is DrupalClientAuthUsernamePassword {
return (
(auth as DrupalClientAuthUsernamePassword)?.username !== undefined ||
(auth as DrupalClientAuthUsernamePassword)?.username !== undefined &&
(auth as DrupalClientAuthUsernamePassword)?.password !== undefined
)
}

function isAccessTokenAuth(
auth: DrupalClientOptions["auth"]
auth: DrupalClientAuth
): auth is DrupalClientAuthAccessToken {
return (auth as DrupalClientAuthAccessToken)?.access_token !== undefined
return (
(auth as DrupalClientAuthAccessToken)?.access_token !== undefined &&
(auth as DrupalClientAuthAccessToken)?.token_type !== undefined
)
}

function isClientIdSecretAuth(
auth: DrupalClient["auth"]
auth: DrupalClientAuth
): auth is DrupalClientAuthClientIdSecret {
return (
(auth as DrupalClientAuthClientIdSecret)?.clientId !== undefined ||
(auth as DrupalClientAuthClientIdSecret)?.clientId !== undefined &&
(auth as DrupalClientAuthClientIdSecret)?.clientSecret !== undefined
)
}
Expand Down Expand Up @@ -177,31 +181,47 @@ export class DrupalClient {

set auth(auth: DrupalClientOptions["auth"]) {
if (typeof auth === "object") {
if (isBasicAuth(auth)) {
if (!auth.username || !auth.password) {
const checkUsernamePassword = auth as DrupalClientAuthUsernamePassword
const checkAccessToken = auth as DrupalClientAuthAccessToken
const checkClientIdSecret = auth as DrupalClientAuthClientIdSecret

if (
checkUsernamePassword.username !== undefined ||
checkUsernamePassword.password !== undefined
) {
if (
!checkUsernamePassword.username ||
!checkUsernamePassword.password
) {
throw new Error(
`'username' and 'password' are required for auth. See https://next-drupal.org/docs/client/auth`
)
}
} else if (isAccessTokenAuth(auth)) {
if (!auth.access_token || !auth.token_type) {
} else if (
checkAccessToken.access_token !== undefined ||
checkAccessToken.token_type !== undefined
) {
if (!checkAccessToken.access_token || !checkAccessToken.token_type) {
throw new Error(
`'access_token' and 'token_type' are required for auth. See https://next-drupal.org/docs/client/auth`
)
}
} else if (!auth.clientId || !auth.clientSecret) {
} else if (
!checkClientIdSecret.clientId ||
!checkClientIdSecret.clientSecret
) {
throw new Error(
`'clientId' and 'clientSecret' are required for auth. See https://next-drupal.org/docs/client/auth`
)
}

auth = {
url: DEFAULT_AUTH_URL,
this._auth = {
...(isClientIdSecretAuth(auth) ? { url: DEFAULT_AUTH_URL } : {}),
...auth,
}
} else {
this._auth = auth
}

this._auth = auth
}

set headers(value: DrupalClientOptions["headers"]) {
Expand Down Expand Up @@ -1356,26 +1376,25 @@ export class DrupalClient {
return this.accessToken
}

if (!opts?.clientId || !opts?.clientSecret) {
if (typeof this._auth === "undefined") {
throw new Error(
"auth is not configured. See https://next-drupal.org/docs/client/auth"
)
let auth: DrupalClientAuthClientIdSecret
if (isClientIdSecretAuth(opts)) {
auth = {
url: DEFAULT_AUTH_URL,
...opts,
}
}

if (
!isClientIdSecretAuth(this._auth) ||
(opts && !isClientIdSecretAuth(opts))
) {
} else if (isClientIdSecretAuth(this._auth)) {
auth = this._auth
} else if (typeof this._auth === "undefined") {
throw new Error(
"auth is not configured. See https://next-drupal.org/docs/client/auth"
)
} else {
throw new Error(
`'clientId' and 'clientSecret' required. See https://next-drupal.org/docs/client/auth`
)
}

const clientId = opts?.clientId || this._auth.clientId
const clientSecret = opts?.clientSecret || this._auth.clientSecret
const url = this.buildUrl(opts?.url || this._auth.url)
const url = this.buildUrl(auth.url)

if (
this.accessTokenScope === opts?.scope &&
Expand All @@ -1388,7 +1407,9 @@ export class DrupalClient {

this.debug(`Fetching new access token.`)

const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64")
const basic = Buffer.from(`${auth.clientId}:${auth.clientSecret}`).toString(
"base64"
)

let body = `grant_type=client_credentials`

Expand Down
5 changes: 2 additions & 3 deletions packages/next-drupal/tests/DrupalClient/constructor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Jsona } from "jsona"
import { DrupalClient } from "../../src"
import { DEBUG_MESSAGE_PREFIX, logger as defaultLogger } from "../../src/logger"
import { BASE_URL } from "../utils"
import type { Logger } from "../../src"
import type { DrupalClientAuth, Logger } from "../../src"

afterEach(() => {
jest.restoreAllMocks()
Expand Down Expand Up @@ -106,7 +106,7 @@ describe("options parameter", () => {
})

test("sets the auth credentials", () => {
const auth: DrupalClient["auth"] = {
const auth: DrupalClientAuth = {
username: "example",
password: "pw",
}
Expand All @@ -115,7 +115,6 @@ describe("options parameter", () => {
})
expect(client._auth).toMatchObject({
...auth,
url: "/oauth/token",
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,19 +278,19 @@ describe("getAccessToken()", () => {
expect(fetchSpy).toHaveBeenCalledTimes(0)
})

test("BUG: throws if auth is ClientIdSecret and not given as opts", async () => {
test("throws if auth is not ClientIdSecret", async () => {
const fetchSpy = spyOnFetch({
responseBody: accessToken,
})

const client = new DrupalClient(BASE_URL, {
auth: clientIdSecret,
auth: mocks.auth.basicAuth,
withAuth: true,
})

await expect(
// @ts-ignore
client.getAccessToken({ scope: "irrelevant" })
client.getAccessToken()
).rejects.toThrow(
"'clientId' and 'clientSecret' required. See https://next-drupal.org/docs/client/auth"
)
Expand Down
17 changes: 8 additions & 9 deletions packages/next-drupal/tests/DrupalClient/getters-setters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,12 @@ describe("auth", () => {
})

describe("throws an error if invalid Access Token", () => {
// TODO: The wrong error is thrown.
test.skip("missing access_token", () => {
test("missing access_token", () => {
expect(() => {
const client = new DrupalClient(BASE_URL)
// @ts-ignore
client.auth = {
token_type: "bearer",
token_type: mocks.auth.accessToken.token_type,
}
}).toThrow(
"'access_token' and 'token_type' are required for auth. See https://next-drupal.org/docs/client/auth"
Expand Down Expand Up @@ -151,21 +150,21 @@ describe("auth", () => {
})

test("sets a default access token url", () => {
const accessToken = {
...mocks.auth.accessToken,
const clientIdSecret = {
...mocks.auth.clientIdSecret,
}
const client = new DrupalClient(BASE_URL)
client.auth = accessToken
client.auth = clientIdSecret
expect(client._auth.url).toBe("/oauth/token")
})

test("can override the default access token url", () => {
const accessToken = {
...mocks.auth.accessToken,
const clientIdSecret = {
...mocks.auth.clientIdSecret,
url: "/custom/oauth/token",
}
const client = new DrupalClient(BASE_URL)
client.auth = accessToken
client.auth = clientIdSecret
expect(client._auth.url).toBe("/custom/oauth/token")
})
})
Expand Down

0 comments on commit 5581933

Please sign in to comment.