Fix PO BOX display and security vulnerability

- Add getChurchPhysicalAddress and getChurchPoBox functions to church-core
- Update UniFFI interface to expose new functions
- Add NAPI wrappers in astro-church-website for new functions
- Update Footer and contact page to use separate address fields
- Rebuild native bindings with new functions
- Display physical address and PO BOX on separate lines properly
- Fix Astro security vulnerability (GHSA-xf8x-j4p2-f749)

Resolves the missing PO BOX issue that was caused by newline character
handling problems between Rust and JavaScript in production environments.
This commit is contained in:
Benjamin Slingo 2025-08-26 17:22:25 -04:00
parent 16caf6c3c4
commit 756a755ba6
10 changed files with 118 additions and 66 deletions

View file

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { getChurchName, fetchEventsJson, fetchFeaturedEventsJson, fetchSermonsJson, fetchConfigJson, getMissionStatement, fetchRandomBibleVerseJson, getStreamLiveStatus, getLivestreamUrl, getChurchAddress, getContactPhone, getContactEmail, getFacebookUrl, getYoutubeUrl, getInstagramUrl, submitContactV2Json, validateContactFormJson, fetchLivestreamArchiveJson, fetchBulletinsJson, fetchCurrentBulletinJson, fetchBibleVerseJson, submitEventJson } = nativeBinding
const { getChurchName, fetchEventsJson, fetchFeaturedEventsJson, fetchSermonsJson, fetchConfigJson, getMissionStatement, fetchRandomBibleVerseJson, getStreamLiveStatus, getLivestreamUrl, getChurchAddress, getChurchPhysicalAddress, getChurchPoBox, getContactPhone, getContactEmail, getFacebookUrl, getYoutubeUrl, getInstagramUrl, submitContactV2Json, validateContactFormJson, fetchLivestreamArchiveJson, fetchBulletinsJson, fetchCurrentBulletinJson, fetchBibleVerseJson, submitEventJson } = nativeBinding
module.exports.getChurchName = getChurchName
module.exports.fetchEventsJson = fetchEventsJson
@ -322,6 +322,8 @@ module.exports.fetchRandomBibleVerseJson = fetchRandomBibleVerseJson
module.exports.getStreamLiveStatus = getStreamLiveStatus
module.exports.getLivestreamUrl = getLivestreamUrl
module.exports.getChurchAddress = getChurchAddress
module.exports.getChurchPhysicalAddress = getChurchPhysicalAddress
module.exports.getChurchPoBox = getChurchPoBox
module.exports.getContactPhone = getContactPhone
module.exports.getContactEmail = getContactEmail
module.exports.getFacebookUrl = getFacebookUrl

View file

@ -13,6 +13,8 @@ export declare function fetchRandomBibleVerseJson(): string
export declare function getStreamLiveStatus(): boolean
export declare function getLivestreamUrl(): string
export declare function getChurchAddress(): string
export declare function getChurchPhysicalAddress(): string
export declare function getChurchPoBox(): string
export declare function getContactPhone(): string
export declare function getContactEmail(): string
export declare function getFacebookUrl(): string

View file

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { getChurchName, fetchEventsJson, fetchFeaturedEventsJson, fetchSermonsJson, fetchConfigJson, getMissionStatement, fetchRandomBibleVerseJson, getStreamLiveStatus, getLivestreamUrl, getChurchAddress, getContactPhone, getContactEmail, getFacebookUrl, getYoutubeUrl, getInstagramUrl, submitContactV2Json, validateContactFormJson, fetchLivestreamArchiveJson, fetchBulletinsJson, fetchCurrentBulletinJson, fetchBibleVerseJson, submitEventJson } = nativeBinding
const { getChurchName, fetchEventsJson, fetchFeaturedEventsJson, fetchSermonsJson, fetchConfigJson, getMissionStatement, fetchRandomBibleVerseJson, getStreamLiveStatus, getLivestreamUrl, getChurchAddress, getChurchPhysicalAddress, getChurchPoBox, getContactPhone, getContactEmail, getFacebookUrl, getYoutubeUrl, getInstagramUrl, submitContactV2Json, validateContactFormJson, fetchLivestreamArchiveJson, fetchBulletinsJson, fetchCurrentBulletinJson, fetchBibleVerseJson, submitEventJson } = nativeBinding
module.exports.getChurchName = getChurchName
module.exports.fetchEventsJson = fetchEventsJson
@ -322,6 +322,8 @@ module.exports.fetchRandomBibleVerseJson = fetchRandomBibleVerseJson
module.exports.getStreamLiveStatus = getStreamLiveStatus
module.exports.getLivestreamUrl = getLivestreamUrl
module.exports.getChurchAddress = getChurchAddress
module.exports.getChurchPhysicalAddress = getChurchPhysicalAddress
module.exports.getChurchPoBox = getChurchPoBox
module.exports.getContactPhone = getContactPhone
module.exports.getContactEmail = getContactEmail
module.exports.getFacebookUrl = getFacebookUrl

View file

@ -38,18 +38,18 @@
"license": "MIT"
},
"node_modules/@astrojs/internal-helpers": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.1.tgz",
"integrity": "sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.2.tgz",
"integrity": "sha512-KCkCqR3Goym79soqEtbtLzJfqhTWMyVaizUi35FLzgGSzBotSw8DB1qwsu7U96ihOJgYhDk2nVPz+3LnXPeX6g==",
"license": "MIT"
},
"node_modules/@astrojs/markdown-remark": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.5.tgz",
"integrity": "sha512-MiR92CkE2BcyWf3b86cBBw/1dKiOH0qhLgXH2OXA6cScrrmmks1Rr4Tl0p/lFpvmgQQrP54Pd1uidJfmxGrpWQ==",
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.6.tgz",
"integrity": "sha512-bwylYktCTsLMVoCOEHbn2GSUA3c5KT/qilekBKA3CBng0bo1TYjNZPr761vxumRk9kJGqTOtU+fgCAp5Vwokug==",
"license": "MIT",
"dependencies": {
"@astrojs/internal-helpers": "0.7.1",
"@astrojs/internal-helpers": "0.7.2",
"@astrojs/prism": "3.3.0",
"github-slugger": "^2.0.0",
"hast-util-from-html": "^2.0.3",
@ -86,12 +86,6 @@
"astro": "^5.3.0"
}
},
"node_modules/@astrojs/node/node_modules/@astrojs/internal-helpers": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.2.tgz",
"integrity": "sha512-KCkCqR3Goym79soqEtbtLzJfqhTWMyVaizUi35FLzgGSzBotSw8DB1qwsu7U96ihOJgYhDk2nVPz+3LnXPeX6g==",
"license": "MIT"
},
"node_modules/@astrojs/prism": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz",
@ -1430,60 +1424,60 @@
]
},
"node_modules/@shikijs/core": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.9.2.tgz",
"integrity": "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.11.0.tgz",
"integrity": "sha512-oJwU+DxGqp6lUZpvtQgVOXNZcVsirN76tihOLBmwILkKuRuwHteApP8oTXmL4tF5vS5FbOY0+8seXmiCoslk4g==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.9.2",
"@shikijs/types": "3.11.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
}
},
"node_modules/@shikijs/engine-javascript": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.9.2.tgz",
"integrity": "sha512-kUTRVKPsB/28H5Ko6qEsyudBiWEDLst+Sfi+hwr59E0GLHV0h8RfgbQU7fdN5Lt9A8R1ulRiZyTvAizkROjwDA==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.11.0.tgz",
"integrity": "sha512-6/ov6pxrSvew13k9ztIOnSBOytXeKs5kfIR7vbhdtVRg+KPzvp2HctYGeWkqv7V6YIoLicnig/QF3iajqyElZA==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.9.2",
"@shikijs/types": "3.11.0",
"@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.3"
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.9.2.tgz",
"integrity": "sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.11.0.tgz",
"integrity": "sha512-4DwIjIgETK04VneKbfOE4WNm4Q7WC1wo95wv82PoHKdqX4/9qLRUwrfKlmhf0gAuvT6GHy0uc7t9cailk6Tbhw==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.9.2",
"@shikijs/types": "3.11.0",
"@shikijs/vscode-textmate": "^10.0.2"
}
},
"node_modules/@shikijs/langs": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.9.2.tgz",
"integrity": "sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.11.0.tgz",
"integrity": "sha512-Njg/nFL4HDcf/ObxcK2VeyidIq61EeLmocrwTHGGpOQx0BzrPWM1j55XtKQ1LvvDWH15cjQy7rg96aJ1/l63uw==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.9.2"
"@shikijs/types": "3.11.0"
}
},
"node_modules/@shikijs/themes": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.9.2.tgz",
"integrity": "sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.11.0.tgz",
"integrity": "sha512-BhhWRzCTEk2CtWt4S4bgsOqPJRkapvxdsifAwqP+6mk5uxboAQchc0etiJ0iIasxnMsb764qGD24DK9albcU9Q==",
"license": "MIT",
"dependencies": {
"@shikijs/types": "3.9.2"
"@shikijs/types": "3.11.0"
}
},
"node_modules/@shikijs/types": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.9.2.tgz",
"integrity": "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.11.0.tgz",
"integrity": "sha512-RB7IMo2E7NZHyfkqAuaf4CofyY8bPzjWPjJRzn6SEak3b46fIQyG6Vx5fG/obqkfppQ+g8vEsiD7Uc6lqQt32Q==",
"license": "MIT",
"dependencies": {
"@shikijs/vscode-textmate": "^10.0.2",
@ -1732,14 +1726,14 @@
}
},
"node_modules/astro": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/astro/-/astro-5.13.0.tgz",
"integrity": "sha512-kWahyvrrxUtgxFRRWSH5X0DESvgg4ZZ0fk6tU7blVZRgmj4PY79nzfaXFT+N+Ac2vWXX7eYUPYs1kLLk5IjBfQ==",
"version": "5.13.3",
"resolved": "https://registry.npmjs.org/astro/-/astro-5.13.3.tgz",
"integrity": "sha512-V0mUOUK70UZ7xqXp5Noqse/SREU0P756KgFufBEluq5LkmBejzC2GENMUA2Na+PFwUjemElJtRlpKyrnKpFhSQ==",
"license": "MIT",
"dependencies": {
"@astrojs/compiler": "^2.12.2",
"@astrojs/internal-helpers": "0.7.1",
"@astrojs/markdown-remark": "6.3.5",
"@astrojs/internal-helpers": "0.7.2",
"@astrojs/markdown-remark": "6.3.6",
"@astrojs/telemetry": "3.3.0",
"@capsizecss/unpack": "^2.4.0",
"@oslojs/encoding": "^1.1.0",
@ -5548,17 +5542,17 @@
}
},
"node_modules/shiki": {
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.9.2.tgz",
"integrity": "sha512-t6NKl5e/zGTvw/IyftLcumolgOczhuroqwXngDeMqJ3h3EQiTY/7wmfgPlsmloD8oYfqkEDqxiaH37Pjm1zUhQ==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.11.0.tgz",
"integrity": "sha512-VgKumh/ib38I1i3QkMn6mAQA6XjjQubqaAYhfge71glAll0/4xnt8L2oSuC45Qcr/G5Kbskj4RliMQddGmy/Og==",
"license": "MIT",
"dependencies": {
"@shikijs/core": "3.9.2",
"@shikijs/engine-javascript": "3.9.2",
"@shikijs/engine-oniguruma": "3.9.2",
"@shikijs/langs": "3.9.2",
"@shikijs/themes": "3.9.2",
"@shikijs/types": "3.9.2",
"@shikijs/core": "3.11.0",
"@shikijs/engine-javascript": "3.11.0",
"@shikijs/engine-oniguruma": "3.11.0",
"@shikijs/langs": "3.11.0",
"@shikijs/themes": "3.11.0",
"@shikijs/types": "3.11.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
}

View file

@ -2,7 +2,8 @@
import { SERVICE_TIMES } from '../lib/constants.js';
import {
getChurchName,
getChurchAddress,
getChurchPhysicalAddress,
getChurchPoBox,
getContactPhone,
getContactEmail,
getFacebookUrl,
@ -12,7 +13,8 @@ import {
} from '../lib/bindings.js';
let churchName = 'Church';
let address = '';
let physicalAddress = '';
let poBox = '';
let phone = '';
let email = '';
let facebookUrl = '';
@ -22,7 +24,8 @@ let missionStatement = '';
try {
churchName = getChurchName();
address = getChurchAddress();
physicalAddress = getChurchPhysicalAddress();
poBox = getChurchPoBox();
phone = getContactPhone();
// Get the base email from church-core and make it dynamic based on current domain
@ -146,13 +149,16 @@ const currentYear = new Date().getFullYear();
<!-- Contact Info -->
<div class="space-y-3 mb-6">
{address && (
{(physicalAddress || poBox) && (
<div class="flex items-start space-x-2 text-sm text-gray-300">
<i data-lucide="map-pin" class="w-4 h-4 text-gold-400 mt-0.5 flex-shrink-0"></i>
<div class="leading-relaxed">
{address.split('\n').map(line => (
<div class="mb-1 last:mb-0">{line}</div>
))}
{physicalAddress && (
<div class="mb-1">{physicalAddress}</div>
)}
{poBox && (
<div class="mb-0">{poBox}</div>
)}
</div>
</div>
)}

View file

@ -51,6 +51,16 @@ pub fn get_church_address() -> String {
church_core::get_church_address()
}
#[napi]
pub fn get_church_physical_address() -> String {
church_core::get_church_physical_address()
}
#[napi]
pub fn get_church_po_box() -> String {
church_core::get_church_po_box()
}
#[napi]
pub fn get_contact_phone() -> String {
church_core::get_contact_phone()

View file

@ -17,6 +17,8 @@ export const {
getStreamLiveStatus,
getLivestreamUrl,
getChurchAddress,
getChurchPhysicalAddress,
getChurchPoBox,
getContactPhone,
getContactEmail,
getFacebookUrl,

View file

@ -3,19 +3,22 @@ import MainLayout from '../layouts/MainLayout.astro';
import { SERVICE_TIMES } from '../lib/constants.js';
import {
getChurchName,
getChurchAddress,
getChurchPhysicalAddress,
getChurchPoBox,
getContactPhone,
getContactEmail
} from '../lib/bindings.js';
let churchName = 'Church';
let address = '';
let physicalAddress = '';
let poBox = '';
let phone = '';
let email = '';
try {
churchName = getChurchName();
address = getChurchAddress();
physicalAddress = getChurchPhysicalAddress();
poBox = getChurchPoBox();
phone = getContactPhone();
@ -146,7 +149,7 @@ try {
<!-- Contact Details -->
<div class="space-y-6">
{address && (
{(physicalAddress || poBox) && (
<div class="flex items-start space-x-4">
<div class="w-12 h-12 bg-primary-500 rounded-xl flex items-center justify-center flex-shrink-0">
<i data-lucide="map-pin" class="w-6 h-6 text-white"></i>
@ -154,9 +157,12 @@ try {
<div>
<h3 class="font-semibold text-gray-900 dark:text-white mb-1">Visit Us</h3>
<div class="text-gray-600 dark:text-gray-300">
{address.split('\n').map(line => (
<p class="mb-1 last:mb-0">{line}</p>
))}
{physicalAddress && (
<p class="mb-1">{physicalAddress}</p>
)}
{poBox && (
<p class="mb-0">{poBox}</p>
)}
</div>
</div>
</div>

View file

@ -53,6 +53,8 @@ namespace church_core {
string get_about_text();
string get_donation_url();
string get_church_address();
string get_church_physical_address();
string get_church_po_box();
sequence<f64> get_coordinates();
string get_website_url();
string get_facebook_url();

View file

@ -920,6 +920,32 @@ pub fn get_church_address() -> String {
"9 Hartford Tpke Tolland CT 06084".to_string()
}
/// Get church physical address only
pub fn get_church_physical_address() -> String {
let config_json = fetch_config_json();
if let Ok(config_value) = serde_json::from_str::<serde_json::Value>(&config_json) {
if let Some(church_address) = config_value.get("church_address").and_then(|v| v.as_str()) {
return church_address.to_string();
}
}
"9 Hartford Tpke Tolland CT 06084".to_string()
}
/// Get church PO BOX only
pub fn get_church_po_box() -> String {
let config_json = fetch_config_json();
if let Ok(config_value) = serde_json::from_str::<serde_json::Value>(&config_json) {
if let Some(po_box) = config_value.get("po_box").and_then(|v| v.as_str()) {
return po_box.to_string();
}
}
"".to_string()
}
/// Get coordinates from config coordinates object or return empty vector
pub fn get_coordinates() -> Vec<f64> {
if let Some(config) = get_cached_config() {