mirror of
https://github.com/directus/directus.git
synced 2026-01-23 04:58:00 -05:00
Use root-relative base url for app and extensions (#6923)
* Add Url util class * Use relative base url for app and extensions Also use utils/url when working with PUBLIC_URL in other places.
This commit is contained in:
committed by
GitHub
parent
5970a7b473
commit
7dfc5dc6af
@@ -45,15 +45,13 @@ import { validateStorage } from './utils/validate-storage';
|
||||
import { register as registerWebhooks } from './webhooks';
|
||||
import { session } from './middleware/session';
|
||||
import { flushCaches } from './cache';
|
||||
import { URL } from 'url';
|
||||
import { Url } from './utils/url';
|
||||
|
||||
export default async function createApp(): Promise<express.Application> {
|
||||
validateEnv(['KEY', 'SECRET']);
|
||||
|
||||
try {
|
||||
new URL(env.PUBLIC_URL);
|
||||
} catch {
|
||||
logger.warn('PUBLIC_URL is not a valid URL');
|
||||
if (!new Url(env.PUBLIC_URL).isAbsolute()) {
|
||||
logger.warn('PUBLIC_URL should be a full URL');
|
||||
}
|
||||
|
||||
await validateStorage();
|
||||
@@ -126,11 +124,14 @@ export default async function createApp(): Promise<express.Application> {
|
||||
|
||||
if (env.SERVE_APP) {
|
||||
const adminPath = require.resolve('@directus/app/dist/index.html');
|
||||
const publicUrl = env.PUBLIC_URL.endsWith('/') ? env.PUBLIC_URL : env.PUBLIC_URL + '/';
|
||||
const adminUrl = new Url(env.PUBLIC_URL).addPath('admin');
|
||||
|
||||
// Set the App's base path according to the APIs public URL
|
||||
let html = fse.readFileSync(adminPath, 'utf-8');
|
||||
html = html.replace(/<meta charset="utf-8" \/>/, `<meta charset="utf-8" />\n\t\t<base href="${publicUrl}admin/">`);
|
||||
html = html.replace(
|
||||
/<meta charset="utf-8" \/>/,
|
||||
`<meta charset="utf-8" />\n\t\t<base href="${adminUrl.toString({ rootRelative: true })}/">`
|
||||
);
|
||||
|
||||
app.get('/admin', (req, res) => res.send(html));
|
||||
app.use('/admin', express.static(path.join(adminPath, '..')));
|
||||
|
||||
@@ -32,6 +32,7 @@ import { rollup } from 'rollup';
|
||||
// @ts-expect-error
|
||||
import virtual from '@rollup/plugin-virtual';
|
||||
import alias from '@rollup/plugin-alias';
|
||||
import { Url } from './utils/url';
|
||||
|
||||
let extensions: Extension[] = [];
|
||||
let extensionBundles: Partial<Record<AppExtensionType, string>> = {};
|
||||
@@ -120,14 +121,15 @@ async function generateExtensionBundles() {
|
||||
|
||||
async function getSharedDepsMapping(deps: string[]) {
|
||||
const appDir = await fse.readdir(path.join(resolvePackage('@directus/app'), 'dist'));
|
||||
const adminUrl = env.PUBLIC_URL.endsWith('/') ? env.PUBLIC_URL + 'admin' : env.PUBLIC_URL + '/admin';
|
||||
|
||||
const depsMapping: Record<string, string> = {};
|
||||
for (const dep of deps) {
|
||||
const depName = appDir.find((file) => dep.replace(/\//g, '_') === file.substring(0, file.indexOf('.')));
|
||||
|
||||
if (depName) {
|
||||
depsMapping[dep] = `${adminUrl}/${depName}`;
|
||||
const depUrl = new Url(env.PUBLIC_URL).addPath('admin', depName);
|
||||
|
||||
depsMapping[dep] = depUrl.toString({ rootRelative: true });
|
||||
} else {
|
||||
logger.warn(`Couldn't find shared extension dependency "${dep}"`);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Accountability } from '@directus/shared/types';
|
||||
import getMailer from '../../mailer';
|
||||
import { Transporter, SendMailOptions } from 'nodemailer';
|
||||
import prettier from 'prettier';
|
||||
import { Url } from '../../utils/url';
|
||||
|
||||
const liquidEngine = new Liquid({
|
||||
root: [path.resolve(env.EXTENSIONS_PATH, 'templates'), path.resolve(__dirname, 'templates')],
|
||||
@@ -100,16 +101,15 @@ export class MailService {
|
||||
};
|
||||
|
||||
function getProjectLogoURL(logoID?: string) {
|
||||
let projectLogoURL = env.PUBLIC_URL;
|
||||
if (projectLogoURL.endsWith('/') === false) {
|
||||
projectLogoURL += '/';
|
||||
}
|
||||
const projectLogoUrl = new Url(env.PUBLIC_URL);
|
||||
|
||||
if (logoID) {
|
||||
projectLogoURL += `assets/${logoID}`;
|
||||
projectLogoUrl.addPath('assets', logoID);
|
||||
} else {
|
||||
projectLogoURL += `admin/img/directus-white.png`;
|
||||
projectLogoUrl.addPath('admin', 'img', 'directus-white.png');
|
||||
}
|
||||
return projectLogoURL;
|
||||
|
||||
return projectLogoUrl.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { AbstractServiceOptions, Item, PrimaryKey, Query, SchemaOverview } from
|
||||
import { Accountability } from '@directus/shared/types';
|
||||
import isUrlAllowed from '../utils/is-url-allowed';
|
||||
import { toArray } from '@directus/shared/utils';
|
||||
import { Url } from '../utils/url';
|
||||
import { AuthenticationService } from './authentication';
|
||||
import { ItemsService, MutationOptions } from './items';
|
||||
import { MailService } from './mail';
|
||||
@@ -305,9 +306,9 @@ export class UsersService extends ItemsService {
|
||||
|
||||
const payload = { email, scope: 'invite' };
|
||||
const token = jwt.sign(payload, env.SECRET as string, { expiresIn: '7d' });
|
||||
const inviteURL = url ?? env.PUBLIC_URL + '/admin/accept-invite';
|
||||
const acceptURL = inviteURL + '?token=' + token;
|
||||
const subjectLine = subject ? subject : "You've been invited";
|
||||
const subjectLine = subject ?? "You've been invited";
|
||||
const inviteURL = url ? new Url(url) : new Url(env.PUBLIC_URL).addPath('admin', 'accept-invite');
|
||||
inviteURL.setQuery('token', token);
|
||||
|
||||
await mailService.send({
|
||||
to: email,
|
||||
@@ -315,7 +316,7 @@ export class UsersService extends ItemsService {
|
||||
template: {
|
||||
name: 'user-invitation',
|
||||
data: {
|
||||
url: acceptURL,
|
||||
url: inviteURL.toString(),
|
||||
email,
|
||||
},
|
||||
},
|
||||
|
||||
78
api/src/utils/url.ts
Normal file
78
api/src/utils/url.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { URL } from 'url';
|
||||
|
||||
export class Url {
|
||||
protocol: string | null;
|
||||
host: string | null;
|
||||
port: string | null;
|
||||
path: string[];
|
||||
query: Record<string, string>;
|
||||
hash: string | null;
|
||||
|
||||
constructor(url: string) {
|
||||
const parsedUrl = new URL(url, 'http://localhost');
|
||||
|
||||
const isProtocolRelative = /^\/\//.test(url);
|
||||
const isRootRelative = /^\/$|^\/[^/]/.test(url);
|
||||
const isPathRelative = /^\./.test(url);
|
||||
|
||||
this.protocol =
|
||||
!isProtocolRelative && !isRootRelative && !isPathRelative
|
||||
? parsedUrl.protocol.substring(0, parsedUrl.protocol.length - 1)
|
||||
: null;
|
||||
this.host = !isRootRelative && !isPathRelative ? parsedUrl.host : null;
|
||||
this.port = parsedUrl.port !== '' ? parsedUrl.port : null;
|
||||
this.path = parsedUrl.pathname.split('/').filter((p) => p !== '');
|
||||
this.query = Object.fromEntries(parsedUrl.searchParams.entries());
|
||||
this.hash = parsedUrl.hash !== '' ? parsedUrl.hash.substring(1) : null;
|
||||
}
|
||||
|
||||
public isAbsolute(): boolean {
|
||||
return this.protocol !== null && this.host !== null;
|
||||
}
|
||||
|
||||
public isProtocolRelative(): boolean {
|
||||
return this.protocol === null && this.host !== null;
|
||||
}
|
||||
|
||||
public isRootRelative(): boolean {
|
||||
return this.protocol === null && this.host === null;
|
||||
}
|
||||
|
||||
public addPath(...paths: string[]): Url {
|
||||
const pathToAdd = paths.flatMap((p) => p.split('/')).filter((p) => p !== '');
|
||||
|
||||
for (const pathSegment of pathToAdd) {
|
||||
if (pathSegment === '..') {
|
||||
this.path.pop();
|
||||
} else if (pathSegment !== '.') {
|
||||
this.path.push(pathSegment);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public setQuery(key: string, value: string): Url {
|
||||
this.query[key] = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public toString({ rootRelative } = { rootRelative: false }): string {
|
||||
const protocol = this.protocol !== null ? `${this.protocol}:` : '';
|
||||
const host = this.host ?? '';
|
||||
const port = this.port !== null ? `:${this.port}` : '';
|
||||
const origin = `${this.host !== null ? `${protocol}//` : ''}${host}${port}`;
|
||||
|
||||
const path = `/${this.path.join('/')}`;
|
||||
const query =
|
||||
Object.keys(this.query).length !== 0
|
||||
? `?${Object.entries(this.query)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('&')}`
|
||||
: '';
|
||||
const hash = this.hash !== null ? `#${this.hash}` : '';
|
||||
|
||||
return `${!rootRelative ? origin : ''}${path}${query}${hash}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user