feat: expose PWA, app identity, and extension directory keys in JSON config #312

This commit is contained in:
Linus Rath
2026-05-21 23:35:58 +02:00
parent 9b22ef810e
commit fc5f6f43d6
58 changed files with 159 additions and 130 deletions
+1 -1
View File
@@ -90,7 +90,7 @@ function OAuthCallbackInner() {
if (mobileRedirectUri && mobileRedirectUri.startsWith("bulwarkmobile://")) {
// Drive /api/auth/sso/complete directly so we can read the tokens
// out of the response loginWithServerSso would consume them and
// out of the response - loginWithServerSso would consume them and
// wire up the webmail auth store, which isn't useful here. The
// server's mobile-flow branch (keyed on the pending cookie) skips
// the refresh-token cookie write for the same reason.
+2 -2
View File
@@ -136,7 +136,7 @@ export default function FilesPage() {
// Initialize JMAP files client. In the Pro shell, all connected accounts
// are surfaced as top-level folders at the root, so we *don't* auto-attach
// to the active account the user picks one explicitly.
// to the active account - the user picks one explicitly.
useEffect(() => {
if (!isAuthenticated || !client || hasFetched.current) return;
hasFetched.current = true;
@@ -397,7 +397,7 @@ export default function FilesPage() {
const currentFilesAccountId = useFileStore((s) => s.currentAccountId);
// Pro shell only: all connected accounts are equal top-level entries at
// the root. The root path "/" itself is a cross-account picker no
// the root. The root path "/" itself is a cross-account picker - no
// account's files are shown until the user enters one.
const accountFolders = isEmbedded
? accounts
+2 -2
View File
@@ -351,7 +351,7 @@ export default function LoginPage() {
const redirectUri = `${window.location.origin}${prefix}/${params.locale}/auth/callback`;
// In mobile-handoff mode the callback page needs to know it should
// redirect into the app rather than into /mail. Stash the params in
// sessionStorage so the same-tab callback can read them the SSO
// sessionStorage so the same-tab callback can read them - the SSO
// pending cookie carries the authoritative copy server-side too.
if (isMobileHandoff) {
try {
@@ -623,7 +623,7 @@ export default function LoginPage() {
saveUsername(formData.username);
if (isMobileHandoff) {
// The isAuthenticated effect handles the redirect; nothing else to
// do here. Don't push to / that would race the deep link.
// do here. Don't push to / - that would race the deep link.
return;
}
router.push('/');
+5 -5
View File
@@ -58,8 +58,8 @@ interface PaneProps {
function Pane({ paneId, tabs, activeTabId, loadedTabIds, onPaneFocus, isFocused }: PaneProps) {
const paneRef = useRef<HTMLDivElement | null>(null);
// Measured pane width, published to children via PaneSizeContext so that
// useDeviceDetection / useIsMobile / etc. branch on pane width not full
// viewport and inner pages collapse to their mobile/tablet layouts when
// useDeviceDetection / useIsMobile / etc. branch on pane width - not full
// viewport - and inner pages collapse to their mobile/tablet layouts when
// the pane is narrow.
const [paneWidth, setPaneWidth] = useState<number | null>(null);
@@ -268,7 +268,7 @@ export default function ProHome() {
// Stable keys are essential: when the split collapses, the row's child
// list goes from [splitPane, divider, mainPane] (or the leading variant)
// to [mainPane]. Without keys, React would reuse the Pane instance at
// index 0 repurposing the *split* pane's instance into the main pane,
// index 0 - repurposing the *split* pane's instance into the main pane,
// which strands the main pane's ResizeObserver/paneWidth on a now-
// unmounted DOM node and reparents the mail tab body (causing remount
// + stale "still-narrow" measurements after the split is closed).
@@ -316,7 +316,7 @@ export default function ProHome() {
<EmbeddedContext.Provider value={true}>
<div className="flex flex-col h-dvh bg-background overflow-hidden pt-[env(safe-area-inset-top)]">
<div className="flex flex-1 overflow-hidden">
{/* Leftmost Navigation Rail identical to the standard layout */}
{/* Leftmost Navigation Rail - identical to the standard layout */}
<div
className="w-14 bg-secondary flex flex-col flex-shrink-0"
style={{ borderRight: '1px solid rgba(128, 128, 128, 0.3)' }}
@@ -357,7 +357,7 @@ export default function ProHome() {
onDragStateChange={setIsTabDragging}
/>
{/* Panes container accepts body drops for split/move. */}
{/* Panes container - accepts body drops for split/move. */}
<div
className="relative flex flex-row flex-1 overflow-hidden min-w-0"
onDragOver={handleBodyDragOver}
+2 -2
View File
@@ -6,7 +6,7 @@ import { apiFetch } from '@/lib/browser-navigation';
interface ConfigEntry {
// Sensitive keys (sessionSecret, oauthClientSecret) come back with
// `value` omitted and `hasValue` set instead the server never echoes
// `value` omitted and `hasValue` set instead - the server never echoes
// the raw secret to the client.
value?: unknown;
source: 'admin' | 'env' | 'default';
@@ -271,7 +271,7 @@ export function AuthTab() {
<Toggle label="OAuth Enabled" configKey="oauthEnabled" value={currentValue('oauthEnabled') as boolean} source={config.oauthEnabled?.source} onChange={handleChange} onRevert={handleRevert} />
<Toggle label="OAuth Only" description="Hide password login form when enabled" configKey="oauthOnly" value={currentValue('oauthOnly') as boolean} source={config.oauthOnly?.source} onChange={handleChange} onRevert={handleRevert} />
<Text label="OAuth Client ID" configKey="oauthClientId" value={currentValue('oauthClientId') as string} source={config.oauthClientId?.source} onChange={handleChange} onRevert={handleRevert} />
<Text label="OAuth Client Secret" configKey="oauthClientSecret" value={currentValue('oauthClientSecret') as string} source={config.oauthClientSecret?.source} onChange={handleChange} onRevert={handleRevert} type="password" placeholder={config.oauthClientSecret?.hasValue ? '•••••••• (saved type to replace)' : undefined} />
<Text label="OAuth Client Secret" configKey="oauthClientSecret" value={currentValue('oauthClientSecret') as string} source={config.oauthClientSecret?.source} onChange={handleChange} onRevert={handleRevert} type="password" placeholder={config.oauthClientSecret?.hasValue ? '•••••••• (saved - type to replace)' : undefined} />
<Text label="OAuth Issuer URL" configKey="oauthIssuerUrl" value={currentValue('oauthIssuerUrl') as string} source={config.oauthIssuerUrl?.source} onChange={handleChange} onRevert={handleRevert} placeholder="https://auth.example.com" />
<Text label="OAuth Scopes" description="Space-separated scopes that replace the defaults. Leave blank to use the built-in scope list." configKey="oauthScopes" value={currentValue('oauthScopes') as string} source={config.oauthScopes?.source} onChange={handleChange} onRevert={handleRevert} placeholder="openid email offline_access" />
<Text label="OAuth Extra Scopes" description="Additional space-separated scopes appended to the defaults." configKey="oauthExtraScopes" value={currentValue('oauthExtraScopes') as string} source={config.oauthExtraScopes?.source} onChange={handleChange} onRevert={handleRevert} placeholder="urn:ietf:params:oauth:..." />