URL generation (front&back) + fixed authJWT for SSR
This commit is contained in:
parent
630ae1df1d
commit
5a3978874a
|
@ -45,8 +45,8 @@ class SubscriptionController extends Controller
|
||||||
$checkout = $checkoutBuilder
|
$checkout = $checkoutBuilder
|
||||||
->collectTaxIds()
|
->collectTaxIds()
|
||||||
->checkout([
|
->checkout([
|
||||||
'success_url' => url('/subscriptions/success'),
|
'success_url' => front_url('/subscriptions/success'),
|
||||||
'cancel_url' => url('/subscriptions/error'),
|
'cancel_url' => front_url('/subscriptions/error'),
|
||||||
'billing_address_collection' => 'required',
|
'billing_address_collection' => 'required',
|
||||||
'customer_update' => [
|
'customer_update' => [
|
||||||
'address' => 'auto',
|
'address' => 'auto',
|
||||||
|
|
|
@ -8,6 +8,7 @@ use Tymon\JWTAuth\Exceptions\JWTException;
|
||||||
|
|
||||||
class AuthenticateJWT
|
class AuthenticateJWT
|
||||||
{
|
{
|
||||||
|
const API_SERVER_SECRET_HEADER_NAME = 'x-api-secret';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the JWT token and validates the IP and User Agent
|
* Verifies the JWT token and validates the IP and User Agent
|
||||||
|
@ -24,6 +25,13 @@ class AuthenticateJWT
|
||||||
|
|
||||||
// Validate IP and User Agent
|
// Validate IP and User Agent
|
||||||
if ($payload) {
|
if ($payload) {
|
||||||
|
if ($frontApiSecret = $request->header(self::API_SERVER_SECRET_HEADER_NAME)) {
|
||||||
|
// If it's a trusted SSR request, skip the rest
|
||||||
|
if ($frontApiSecret === config('app.front_api_secret')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$error = null;
|
$error = null;
|
||||||
if (!\Hash::check($request->ip(), $payload->get('ip'))) {
|
if (!\Hash::check($request->ip(), $payload->get('ip'))) {
|
||||||
$error = 'Origin IP is invalid';
|
$error = 'Origin IP is invalid';
|
||||||
|
|
|
@ -164,14 +164,14 @@ class StoreFormSubmissionJob implements ShouldQueue
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(filter_var($value, FILTER_VALIDATE_URL) !== FALSE && str_contains($value, parse_url(config('app.url'))['host'])) { // In case of prefill we have full url so convert to s3
|
if(filter_var($value, FILTER_VALIDATE_URL) !== false && str_contains($value, parse_url(config('app.url'))['host'])) { // In case of prefill we have full url so convert to s3
|
||||||
$fileName = basename($value);
|
$fileName = basename($value);
|
||||||
$path = FormController::ASSETS_UPLOAD_PATH . '/' . $fileName;
|
$path = FormController::ASSETS_UPLOAD_PATH . '/' . $fileName;
|
||||||
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
|
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
|
||||||
Storage::move($path, $newPath.'/'.$fileName);
|
Storage::move($path, $newPath.'/'.$fileName);
|
||||||
return $fileName;
|
return $fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->isSkipForUpload($value)) {
|
if($this->isSkipForUpload($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,12 +157,12 @@ class Form extends Model implements CachableAttributes
|
||||||
if ($this->custom_domain) {
|
if ($this->custom_domain) {
|
||||||
return 'https://' . $this->custom_domain . '/forms/' . $this->slug;
|
return 'https://' . $this->custom_domain . '/forms/' . $this->slug;
|
||||||
}
|
}
|
||||||
return '/forms/' . $this->slug;
|
return front_url('/forms/' . $this->slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEditUrlAttribute()
|
public function getEditUrlAttribute()
|
||||||
{
|
{
|
||||||
return url('/forms/' . $this->slug . '/show');
|
return front_url('/forms/' . $this->slug . '/show');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubmissionsCountAttribute()
|
public function getSubmissionsCountAttribute()
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Template extends Model
|
||||||
|
|
||||||
public function getShareUrlAttribute()
|
public function getShareUrlAttribute()
|
||||||
{
|
{
|
||||||
return url('/form-templates/'.$this->slug);
|
return front_url('/form-templates/'.$this->slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDescriptionAttribute($value)
|
public function setDescriptionAttribute($value)
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ResetPassword extends Notification
|
||||||
{
|
{
|
||||||
return (new MailMessage)
|
return (new MailMessage)
|
||||||
->line('You are receiving this email because we received a password reset request for your account.')
|
->line('You are receiving this email because we received a password reset request for your account.')
|
||||||
->action('Reset Password', url('password/reset/'.$this->token).'?email='.urlencode($notifiable->email))
|
->action('Reset Password', front_url('password/reset/'.$this->token).'?email='.urlencode($notifiable->email))
|
||||||
->line('If you did not request a password reset, no further action is required.');
|
->line('If you did not request a password reset, no further action is required.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,6 @@ class FailedPaymentNotification extends Notification implements ShouldQueue
|
||||||
->line(__('Please go to OpenForm, click on your name on the top right corner, and click on "Billing".
|
->line(__('Please go to OpenForm, click on your name on the top right corner, and click on "Billing".
|
||||||
You will then be able to update your card details. To avoid any service disruption, you can reply to this email whenever
|
You will then be able to update your card details. To avoid any service disruption, you can reply to this email whenever
|
||||||
you updated your card details, and we\'ll manually attempt to charge your card.'))
|
you updated your card details, and we\'ll manually attempt to charge your card.'))
|
||||||
->action(__('Go to OpenForm'), url('/'));
|
->action(__('Go to OpenForm'), front_url('/'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class DiscordHandler extends AbstractWebhookHandler
|
||||||
$externalLinks[] = '[**🔗 Open Form**](' . $this->form->share_url . ')';
|
$externalLinks[] = '[**🔗 Open Form**](' . $this->form->share_url . ')';
|
||||||
}
|
}
|
||||||
if(Arr::get($settings, 'link_edit_form', true)){
|
if(Arr::get($settings, 'link_edit_form', true)){
|
||||||
$editFormURL = url('forms/' . $this->form->slug . '/show');
|
$editFormURL = front_url('forms/' . $this->form->slug . '/show');
|
||||||
$externalLinks[] = '[**✍️ Edit Form**](' . $editFormURL . ')';
|
$externalLinks[] = '[**✍️ Edit Form**](' . $editFormURL . ')';
|
||||||
}
|
}
|
||||||
if (Arr::get($settings, 'link_edit_submission', true) && $this->form->editable_submissions) {
|
if (Arr::get($settings, 'link_edit_submission', true) && $this->form->editable_submissions) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ class SlackHandler extends AbstractWebhookHandler
|
||||||
$externalLinks[] = '*<' . $this->form->share_url . '|🔗 Open Form>*';
|
$externalLinks[] = '*<' . $this->form->share_url . '|🔗 Open Form>*';
|
||||||
}
|
}
|
||||||
if(Arr::get($settings, 'link_edit_form', true)){
|
if(Arr::get($settings, 'link_edit_form', true)){
|
||||||
$editFormURL = url('forms/' . $this->form->slug . '/show');
|
$editFormURL = front_url('forms/' . $this->form->slug . '/show');
|
||||||
$externalLinks[] = '*<' . $editFormURL . '|✍️ Edit Form>*';
|
$externalLinks[] = '*<' . $editFormURL . '|✍️ Edit Form>*';
|
||||||
}
|
}
|
||||||
if (Arr::get($settings, 'link_edit_submission', true) && $this->form->editable_submissions) {
|
if (Arr::get($settings, 'link_edit_submission', true) && $this->form->editable_submissions) {
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function front_url($path = '')
|
||||||
|
{
|
||||||
|
$baseUrl = config('app.front_url');
|
||||||
|
if (!$baseUrl) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rtrim($baseUrl, '/'). '/' . ltrim($path, '/');
|
||||||
|
}
|
|
@ -103,6 +103,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineProps, computed } from 'vue'
|
import { ref, defineProps, computed } from 'vue'
|
||||||
|
import {appUrl} from "~/lib/utils.js";
|
||||||
|
|
||||||
const { copy } = useClipboard()
|
const { copy } = useClipboard()
|
||||||
const crisp = useCrisp()
|
const crisp = useCrisp()
|
||||||
|
@ -135,7 +136,7 @@ let embedPopupCode = computed(() => {
|
||||||
width: advancedOptions.value.width
|
width: advancedOptions.value.width
|
||||||
}
|
}
|
||||||
previewPopup(nfData)
|
previewPopup(nfData)
|
||||||
return '<script async data-nf=\'' + JSON.stringify(nfData) + '\' src=\'' + embedScriptUrl + '\'></scrip' + 't>'
|
return '<script async data-nf=\'' + JSON.stringify(nfData) + '\' src=\'' + appUrl(embedScriptUrl) + '\'></scrip' + 't>'
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -18,13 +18,18 @@ function addPasswordToFormRequest(request, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOpnRequestsOptions(request, opts) {
|
export function getOpnRequestsOptions(request, opts) {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
opts.headers = {accept: 'application/json', ...opts.headers}
|
opts.headers = {accept: 'application/json', ...opts.headers}
|
||||||
|
|
||||||
|
// Authenticate requests coming from the server
|
||||||
|
if (process.server && config.apiSecret) {
|
||||||
|
opts.headers['x-api-secret'] = config.apiSecret
|
||||||
|
}
|
||||||
|
|
||||||
addAuthHeader(request, opts)
|
addAuthHeader(request, opts)
|
||||||
addPasswordToFormRequest(request, opts)
|
addPasswordToFormRequest(request, opts)
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL: config.public.apiBase,
|
baseURL: config.public.apiBase,
|
||||||
onResponseError({response}) {
|
onResponseError({response}) {
|
||||||
|
|
|
@ -13,3 +13,21 @@ export const hash = (str, seed = 0) => {
|
||||||
|
|
||||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const appUrl = (path = '/') => {
|
||||||
|
let baseUrl = useRuntimeConfig().public.appUrl
|
||||||
|
if (!baseUrl) {
|
||||||
|
console.warn('appUrl not set in runtimeConfig')
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.startsWith('/')) {
|
||||||
|
path = path.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!baseUrl.endsWith('/')) {
|
||||||
|
baseUrl += '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl + path
|
||||||
|
}
|
||||||
|
|
|
@ -86,11 +86,11 @@
|
||||||
class="w-full mt-12 relative px-6 mx-auto max-w-4xl sm:px-10 lg:px-0 z-10 flex items-center justify-center"
|
class="w-full mt-12 relative px-6 mx-auto max-w-4xl sm:px-10 lg:px-0 z-10 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="-m-2 rounded-xl bg-blue-900/5 p-2 backdrop-blur-sm ring-1 ring-inset ring-blue-900/10 lg:-m-4 lg:rounded-2xl lg:p-4"
|
class="-m-2 rounded-xl bg-blue-900/5 p-2 backdrop-blur-sm ring-1 ring-inset ring-blue-900/10 lg:-m-4 lg:rounded-2xl lg:p-4 w-full"
|
||||||
>
|
>
|
||||||
<NuxtImg src="/img/pages/welcome/product-cover.jpg" placeholder
|
<NuxtImg src="/img/pages/welcome/product-cover.jpg"
|
||||||
sizes="320px sm:650px lg:900px"
|
sizes="320px sm:650px lg:896px"
|
||||||
alt="Product screenshot" loading="lazy" class="rounded-md shadow-2xl ring-1 ring-gray-900/10"
|
alt="Product screenshot" loading="lazy" class="rounded-md w-full shadow-2xl ring-1 ring-gray-900/10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,5 +13,10 @@ export default {
|
||||||
s3Enabled: process.env.NUXT_PUBLIC_S3_ENABLED || false,
|
s3Enabled: process.env.NUXT_PUBLIC_S3_ENABLED || false,
|
||||||
paidPlansEnabled: process.env.NUXT_PUBLIC_PAID_PLANS_ENABLED || false,
|
paidPlansEnabled: process.env.NUXT_PUBLIC_PAID_PLANS_ENABLED || false,
|
||||||
customDomainsEnabled: process.env.NUXT_PUBLIC_CUSTOM_DOMAINS_ENABLED || false,
|
customDomainsEnabled: process.env.NUXT_PUBLIC_CUSTOM_DOMAINS_ENABLED || false,
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to authenticate that the requests are coming from the server - not from a client.
|
||||||
|
*/
|
||||||
|
apiSecret: process.env.NUXT_API_SECRET || '',
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,10 @@
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
"Database\\Factories\\": "database/factories/",
|
"Database\\Factories\\": "database/factories/",
|
||||||
"Database\\Seeders\\": "database/seeders/"
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
}
|
},
|
||||||
|
"files": [
|
||||||
|
"app/helpers.php"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
|
@ -56,6 +56,15 @@ return [
|
||||||
|
|
||||||
'asset_url' => env('ASSET_URL', null),
|
'asset_url' => env('ASSET_URL', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Application Client (Nuxt Front-end)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
'front_url' => env('FRONT_URL', null),
|
||||||
|
'front_api_secret' => env('FRONT_API_SECRET', null),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Application Timezone
|
| Application Timezone
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{{-- Header --}}
|
{{-- Header --}}
|
||||||
@slot('header')
|
@slot('header')
|
||||||
@if (!(isset($noBranding) && $noBranding))
|
@if (!(isset($noBranding) && $noBranding))
|
||||||
@component('mail::header', ['url' => config('app.url')])
|
@component('mail::header', ['url' => front_url()])
|
||||||
{{ config('app.name') }}
|
{{ config('app.name') }}
|
||||||
@endcomponent
|
@endcomponent
|
||||||
@endif
|
@endif
|
||||||
|
|
Loading…
Reference in New Issue