Add custom domain support
This commit is contained in:
parent
a0513c4458
commit
ea7041be28
|
@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class CustomDomainRestriction
|
class CustomDomainRestriction
|
||||||
{
|
{
|
||||||
const CUSTOM_DOMAIN_HEADER = "User-Custom-Domain";
|
const CUSTOM_DOMAIN_HEADER = "x-custom-domain";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
|
@ -27,7 +27,8 @@ class CustomDomainRestriction
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Invalid domain',
|
'message' => 'Invalid domain',
|
||||||
], 400);
|
'error' => 'invalid_domain',
|
||||||
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if domain is different from current domain
|
// Check if domain is different from current domain
|
||||||
|
@ -41,6 +42,7 @@ class CustomDomainRestriction
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Unknown domain',
|
'message' => 'Unknown domain',
|
||||||
|
'error' => 'invalid_domain',
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {getDomain, getHost, customDomainUsed} from "~/lib/utils.js";
|
||||||
|
|
||||||
function addAuthHeader(request, options) {
|
function addAuthHeader(request, options) {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
@ -17,6 +18,14 @@ function addPasswordToFormRequest(request, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom domain header if custom domain is used
|
||||||
|
*/
|
||||||
|
function addCustomDomainHeader(request, options) {
|
||||||
|
if (!customDomainUsed()) return
|
||||||
|
options.headers['x-custom-domain'] = getDomain(getHost())
|
||||||
|
}
|
||||||
|
|
||||||
export function getOpnRequestsOptions(request, opts) {
|
export function getOpnRequestsOptions(request, opts) {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
@ -29,17 +38,27 @@ export function getOpnRequestsOptions(request, opts) {
|
||||||
|
|
||||||
addAuthHeader(request, opts)
|
addAuthHeader(request, opts)
|
||||||
addPasswordToFormRequest(request, opts)
|
addPasswordToFormRequest(request, opts)
|
||||||
|
addCustomDomainHeader(request, opts)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL: config.public.apiBase,
|
baseURL: config.public.apiBase,
|
||||||
onResponseError({response}) {
|
onResponseError({response}) {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
console.log(response)
|
||||||
const {status} = response
|
const {status} = response
|
||||||
|
|
||||||
if (status === 401 && authStore.check) {
|
if (status === 401) {
|
||||||
console.log("Logging out due to 401")
|
if (response.body.error && response.body.error === 'invalid_domain' && process.client) {
|
||||||
authStore.logout()
|
// If invalid domain, redirect to main domain
|
||||||
useRouter().push({name: 'login'})
|
window.location.href = config.public.appUrl + '?utm_source=failed_custom_domain_redirect'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authStore.check) {
|
||||||
|
console.log("Logging out due to 401")
|
||||||
|
authStore.logout()
|
||||||
|
useRouter().push({name: 'login'})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status >= 500) {
|
if (status >= 500) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
export const hash = (str, seed = 0) => {
|
export const hash = (str, seed = 0) => {
|
||||||
let h1 = 0xdeadbeef ^ seed,
|
let h1 = 0xdeadbeef ^ seed,
|
||||||
h2 = 0x41c6ce57 ^ seed;
|
h2 = 0x41c6ce57 ^ seed;
|
||||||
|
@ -14,6 +13,15 @@ export const hash = (str, seed = 0) => {
|
||||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Url and domain related utils
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the appUrl with the given path appended.
|
||||||
|
* @param path
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
export const appUrl = (path = '/') => {
|
export const appUrl = (path = '/') => {
|
||||||
let baseUrl = useRuntimeConfig().public.appUrl
|
let baseUrl = useRuntimeConfig().public.appUrl
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
|
@ -31,3 +39,36 @@ export const appUrl = (path = '/') => {
|
||||||
|
|
||||||
return baseUrl + path
|
return baseUrl + path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSR compatible function to get current host
|
||||||
|
* @param path
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const getHost = function () {
|
||||||
|
if (process.server) {
|
||||||
|
return useNuxtApp().ssrContext?.event.context.siteConfigNitroOrigin || useNuxtApp().ssrContext?.event.node.req.headers.host
|
||||||
|
} else {
|
||||||
|
return window.location.host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract domain from url
|
||||||
|
* @param url
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export const getDomain = function (url) {
|
||||||
|
return (new URL(url)).hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the app is running on a custom domain, false otherwise.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const customDomainUsed = function() {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const appUrl = config.public.appUrl
|
||||||
|
|
||||||
|
return getDomain(getHost()) !== getDomain(appUrl)
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,42 @@
|
||||||
import opnformConfig from "~/opnform.config.js";
|
import {customDomainUsed, getDomain, getHost} from "~/lib/utils.js";
|
||||||
|
|
||||||
function getDomain (url) {
|
/**
|
||||||
return (new URL(url)).hostname
|
* Added by Caddy when proxying to the app
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const customDomainHeaderName = 'CUSTOM_DOMAIN_HEADER'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of routes that can be used with a custom domain
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
const customDomainAllowedRoutes = ['forms-slug']
|
||||||
|
|
||||||
|
function redirectToMainDomain() {
|
||||||
|
return navigateTo(useRuntimeConfig().public.appUrl + '?utm_source=failed_custom_domain_redirect', { redirectCode: 301, external: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware((to, from) => {
|
export default defineNuxtRouteMiddleware((to, from) => {
|
||||||
if (opnformConfig.custom_domains_enabled && process.client) {
|
if (process.client) return
|
||||||
const isCustomDomain = getDomain(window.location.href) !== getDomain(opnformConfig.app_url)
|
|
||||||
if (isCustomDomain && !['forms.show_public'].includes(to.name)) {
|
const config = useRuntimeConfig()
|
||||||
// If route isn't a public form, redirect
|
|
||||||
return navigateTo({name: 'home',query: {utm_source: 'failed_custom_domain_redirect'}});
|
if (!customDomainUsed()) return
|
||||||
}
|
|
||||||
|
const customDomainHeaderValue = useRequestHeaders()[customDomainHeaderName]
|
||||||
|
if (!customDomainHeaderValue || customDomainHeaderValue !== getDomain(getHost())) {
|
||||||
|
// If custom domain header doesn't match, redirect
|
||||||
|
return redirectToMainDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.public.customDomainsEnabled) {
|
||||||
|
// If custom domain not allowed, redirect
|
||||||
|
return redirectToMainDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!customDomainAllowedRoutes.includes(to.name)) {
|
||||||
|
// Custom domain only allowed for form url
|
||||||
|
return redirectToMainDomain()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
export default defineNitroPlugin(nitroApp => {
|
export default defineNitroPlugin(nitroApp => {
|
||||||
nitroApp.hooks.hook('render:response', (response, { event }) => {
|
nitroApp.hooks.hook('render:response', (response, { event }) => {
|
||||||
const routePath = event.node?.req?.url || event.node?.req?.originalUrl
|
const routePath = event.node?.req?.url || event.node?.req?.originalUrl
|
||||||
console.log(routePath, !routePath.startsWith('/forms/'))
|
|
||||||
// const routePath= event.context.params._
|
// const routePath= event.context.params._
|
||||||
if (routePath && !routePath.startsWith('/forms/')) {
|
if (routePath && !routePath.startsWith('/forms/')) {
|
||||||
console.log(response, event)
|
|
||||||
// Only allow embedding of forms
|
// Only allow embedding of forms
|
||||||
response.headers['X-Frame-Options'] = 'sameorigin'
|
response.headers['X-Frame-Options'] = 'sameorigin'
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,3 +14,7 @@ export default async (to, from, next) => {
|
||||||
function getDomain (url) {
|
function getDomain (url) {
|
||||||
return (new URL(url)).hostname
|
return (new URL(url)).hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCustomDomain (url) {
|
||||||
|
return getDomain(url) !== getDomain(window.config.app_url)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue