Skip to content

NEX-195: Allow the frontend to access the backend via internal url in runtime #289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ commands:

drupal_release_name=`silta ci release name`
next_release_name=`silta ci release name --release-suffix next`
# This is the name that will be used in the internal URL, used in
# Node containers to call the Drupal backend.
drupal_release_name_for_internal_use=${drupal_release_name}-drupal

# If name is too long, truncate it and append a hash
if [ ${#drupal_release_name} -ge 36 ]; then
Expand All @@ -145,6 +148,12 @@ commands:
fi

echo "export NEXT_PUBLIC_DRUPAL_BASE_URL=https://$drupal_domain" >> "$BASH_ENV"
# Here we set the internal URL to the same as the public one, because
# CircleCI is not able to resolve the internal URL.
echo "export DRUPAL_BASE_URL_INTERNAL=https://$drupal_domain" >> "$BASH_ENV"
# This is an additional environment variable used only to allow
# images from the internal domain when running in CircleCI.
echo "export DRUPAL_BASE_URL_INTERNAL_IMAGES=http://$drupal_release_name_for_internal_use" >> "$BASH_ENV"
echo "export NEXT_PUBLIC_FRONTEND_URL=https://$next_domain" >> "$BASH_ENV"
echo "export AUTH_URL=https://$next_domain" >> "$BASH_ENV"
echo "export DRUPAL_CLIENT_ID=$drupal_client_id" >> "$BASH_ENV"
Expand All @@ -153,6 +162,11 @@ commands:

echo "Expanding special variable DRUPAL_DOMAIN = $drupal_domain ..."
sed -i -e "s/<|DRUPAL_DOMAIN|>/$drupal_domain/g" $envvars_file

# We set the release name here to the internal URL
# and this will then be used by the node containers to call the drupal backend.
echo "Expanding special variable DRUPAL_RELEASE_NAME = $drupal_release_name_for_internal_use ..."
sed -i -e "s/<|DRUPAL_RELEASE_NAME|>/$drupal_release_name_for_internal_use/g" $envvars_file

echo "Expanding special variable NEXT_DOMAIN = $next_domain ..."
sed -i -e "s/<|NEXT_DOMAIN|>/$next_domain/g" $envvars_file
Expand Down
2 changes: 2 additions & 0 deletions .ddev/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ web_environment:
- ENVIRONMENT_NAME=ddev
- HASH_SALT=notsosecurehashnotsosecurehashnotsosecurehash
- NEXT_PUBLIC_DRUPAL_BASE_URL=https://next-drupal-starterkit.ddev.site
- DRUPAL_BASE_URL_INTERNAL=http://localhost
- DRUPAL_BASE_URL_INTERNAL_IMAGES=http://localhost
- NEXT_PUBLIC_FRONTEND_URL=https://frontend.ddev.site
# Environment variables for next_auth:
- AUTH_SECRET=nextauth_secret_not_secure_used_only_locally
Expand Down
2 changes: 2 additions & 0 deletions .lando.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ services:
DRUPAL_CLIENT_VIEWER_ID: drupal-client-viewer-id
DRUPAL_CLIENT_VIEWER_SECRET: drupal_client_viewer_secret_not_secure_used_only_locally
NEXT_PUBLIC_DRUPAL_BASE_URL: https://next-drupal-starterkit.lndo.site
DRUPAL_BASE_URL_INTERNAL: http://appserver_nginx
DRUPAL_BASE_URL_INTERNAL_IMAGES: http://appserver_nginx
NEXT_PUBLIC_FRONTEND_URL: https://frontend.lndo.site
# Environment variables for next_auth:
AUTH_SECRET: nextauth_secret_not_secure_used_only_locally
Expand Down
21 changes: 12 additions & 9 deletions next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ import createNextIntlPlugin from "next-intl/plugin";

const withNextIntl = createNextIntlPlugin();

const imageHostname = String(process.env.NEXT_PUBLIC_DRUPAL_BASE_URL).split(
"://",
)[1];

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
Expand All @@ -24,12 +20,19 @@ const nextConfig = {

images: {
remotePatterns: [
{
protocol: "https",
hostname: imageHostname,
process.env.DRUPAL_BASE_URL_INTERNAL_IMAGES,
process.env.NEXT_PUBLIC_DRUPAL_BASE_URL,
].map((url = "") => {
const [protocol, hostname] = url.split("://");
if (!hostname || (protocol !== "https" && protocol !== "http")) {
throw new Error(`Invalid images URL "${url}" in next.config.ts`);
}
return {
protocol,
hostname,
pathname: "**",
},
],
};
}),
},

experimental: {
Expand Down
8 changes: 5 additions & 3 deletions next/src/app/_actions/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { getLocale } from "next-intl/server";

import { drupalClientViewer } from "@/lib/drupal/drupal-client";
import { drupalExternalClientViewer } from "@/lib/drupal/drupal-client";
import {
RegisterFormInputs,
registerFormSchema,
Expand All @@ -24,10 +24,12 @@ export async function registerAction(values: RegisterFormInputs) {
}

try {
const url = drupalClientViewer.buildUrl("/user/register?_format=json");
const url = drupalExternalClientViewer.buildUrl(
"/user/register?_format=json",
);

// Do a call to drupal to register the user:
const result = await drupalClientViewer.fetch(url.toString(), {
const result = await drupalExternalClientViewer.fetch(url.toString(), {
method: "POST",
body: JSON.stringify({
name: [{ value: validatedInputs.data.name }],
Expand Down
2 changes: 1 addition & 1 deletion next/src/app/api/health/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export async function GET() {
try {
// Checking that Drupal is up:
const drupalResponse = await fetch(
`${env.NEXT_PUBLIC_DRUPAL_BASE_URL}/_ping.php`,
`${env.DRUPAL_BASE_URL_INTERNAL}/_ping.php`,
);

const drupalStatus = drupalResponse.status;
Expand Down
4 changes: 2 additions & 2 deletions next/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const {

// Get access token from Drupal.
const response = await drupalClientViewer.fetch(
`${env.NEXT_PUBLIC_DRUPAL_BASE_URL}/oauth/token`,
`${env.DRUPAL_BASE_URL_INTERNAL}/oauth/token`,
{
method: "POST",
body: formData,
Expand Down Expand Up @@ -115,7 +115,7 @@ async function refreshAccessToken(token) {
formData.append("refresh_token", token.refreshToken);

const response = await fetch(
`${env.NEXT_PUBLIC_DRUPAL_BASE_URL}/oauth/token`,
`${env.DRUPAL_BASE_URL_INTERNAL}/oauth/token`,
{
method: "POST",
body: formData,
Expand Down
5 changes: 5 additions & 0 deletions next/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const env = createEnv({
DRUPAL_CLIENT_VIEWER_SECRET: zod.string(),
DRUPAL_REVALIDATE_SECRET: zod.string(),
ES_HOST: zod.string(),
DRUPAL_BASE_URL_INTERNAL: zod.string().url(),
DRUPAL_BASE_URL_INTERNAL_IMAGES: zod.string().url(),
},
client: {
NEXT_PUBLIC_NODE_ENV: zod.enum(["development", "production", "test"]),
Expand All @@ -27,6 +29,9 @@ export const env = createEnv({
DRUPAL_CLIENT_VIEWER_SECRET: process.env.DRUPAL_CLIENT_VIEWER_SECRET,
DRUPAL_REVALIDATE_SECRET: process.env.DRUPAL_REVALIDATE_SECRET,
NEXT_PUBLIC_DRUPAL_BASE_URL: process.env.NEXT_PUBLIC_DRUPAL_BASE_URL,
DRUPAL_BASE_URL_INTERNAL: process.env.DRUPAL_BASE_URL_INTERNAL,
DRUPAL_BASE_URL_INTERNAL_IMAGES:
process.env.DRUPAL_BASE_URL_INTERNAL_IMAGES,
NEXT_PUBLIC_FRONTEND_URL: process.env.NEXT_PUBLIC_FRONTEND_URL,
ES_HOST: process.env.ES_HOST,
},
Expand Down
15 changes: 14 additions & 1 deletion next/src/lib/drupal/drupal-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const RETRY_OPTIONS: Options = {
export const createGraphQlDrupalClient = (
clientId: string,
clientSecret: string,
// The base URL for the Drupal API. This is used to build the URL for the GraphQL endpoint.
// The default is the internal URL, which is used for server-side requests.
drupalBaseUrl: string = env.DRUPAL_BASE_URL_INTERNAL,
) => {
class GraphQlDrupalClient extends NextDrupalBase {
async doGraphQlRequest<T>(
Expand All @@ -43,7 +46,7 @@ export const createGraphQlDrupalClient = (
}
}

return new GraphQlDrupalClient(env.NEXT_PUBLIC_DRUPAL_BASE_URL, {
return new GraphQlDrupalClient(drupalBaseUrl, {
fetcher: (input, init) => pRetry(() => fetch(input, init), RETRY_OPTIONS),
auth: {
clientId,
Expand All @@ -66,3 +69,13 @@ export const drupalClientPreviewer = createGraphQlDrupalClient(
env.DRUPAL_CLIENT_ID,
env.DRUPAL_CLIENT_SECRET,
);

// This instance of the client will connect to the Drupal API using a consumer that
// is associated with a role with "regular" permissions. This will always use the
// external URL for the Drupal API. This is useful, for example, when registering
// users, so that the links in the email confirmation are correct.
export const drupalExternalClientViewer = createGraphQlDrupalClient(
env.DRUPAL_CLIENT_VIEWER_ID,
env.DRUPAL_CLIENT_VIEWER_SECRET,
env.NEXT_PUBLIC_DRUPAL_BASE_URL,
);
2 changes: 2 additions & 0 deletions silta/silta-next.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ services:
DRUPAL_REVALIDATE_SECRET: ${DRUPAL_REVALIDATE_SECRET}
AUTH_SECRET: ${AUTH_SECRET}
NEXT_PUBLIC_DRUPAL_BASE_URL: https://<|DRUPAL_DOMAIN|>
DRUPAL_BASE_URL_INTERNAL: http://<|DRUPAL_RELEASE_NAME|>
DRUPAL_BASE_URL_INTERNAL_IMAGES: http://<|DRUPAL_RELEASE_NAME|>
NEXT_PUBLIC_FRONTEND_URL: https://<|NEXT_DOMAIN|>
AUTH_URL: https://<|NEXT_DOMAIN|>
NEXT_TELEMETRY_DISABLED: 1
Expand Down