URL generation (front&back) + fixed authJWT for SSR

This commit is contained in:
Julien Nahum 2024-01-11 14:07:27 +01:00
parent 630ae1df1d
commit 5a3978874a
18 changed files with 81 additions and 21 deletions

View File

@ -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',

View File

@ -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';

View File

@ -164,7 +164,7 @@ 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);

View File

@ -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()

View File

@ -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)

View File

@ -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.');
} }
} }

View File

@ -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('/'));
} }
} }

View File

@ -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) {

View File

@ -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) {

11
app/helpers.php Normal file
View File

@ -0,0 +1,11 @@
<?php
function front_url($path = '')
{
$baseUrl = config('app.front_url');
if (!$baseUrl) {
return $path;
}
return rtrim($baseUrl, '/'). '/' . ltrim($path, '/');
}

View File

@ -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(() => {

View File

@ -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}) {

18
client/lib/utils.js vendored
View File

@ -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
}

View File

@ -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>

View File

@ -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 || '',
} }

View File

@ -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": {

View File

@ -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

View File

@ -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