feat(mail): withhold new Gmail consents on hosted unless the company is allowlisted (#2320)
Every Gmail consent shows "Google hasn't verified this app" until the restricted-scope review closes, and a prospect bounced on it today. Jakob's call: remove the connector in the meantime rather than explain the screen. New consents are gated by GOOGLE_MAIL_CONNECT_COMPANY_IDS on hosted: unset means nobody (the default from this deploy on), `*` means everybody (set once Google approves), a comma list means those companies (the reviewer's demo company, the company the video is recorded in). Enforced in /oauth/start (403 connect_disabled) and mirrored as connectEnabled on /connections, so the settings page drops its connect button and the inbox start card falls back to plain upload. Existing mailboxes stay listed, keep being searched and can be disconnected. Self-hosted installs run their own Google app and are never gated. Claude-Session: https://claude.ai/code/session_01UD3HsDX8hnJEqpt35azxBJ Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5.1
parent
3e5670f978
commit
c0eda46354
@@ -670,6 +670,7 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
// the mailboxes we search, WhatsApp for photographed receipts, and the
|
||||
// forwarding address that works with nothing connected at all.
|
||||
const [mailConnections, setMailConnections] = useState<InboxMailConnection[]>([])
|
||||
const [mailConnectEnabled, setMailConnectEnabled] = useState(false)
|
||||
const [whatsapp, setWhatsapp] = useState<{ linked: boolean; phoneMasked?: string; verifiedAt?: string | null } | null>(null)
|
||||
const [sourcesOpen, setSourcesOpen] = useState(false)
|
||||
// Received-mail history (#2181): read when its panel is first opened, so
|
||||
@@ -701,8 +702,13 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
try {
|
||||
const res = await fetch('/api/extensions/ext/mail/connections')
|
||||
if (!res.ok) return
|
||||
const json = (await res.json()) as { data?: { connections?: InboxMailConnection[] } }
|
||||
const json = (await res.json()) as {
|
||||
data?: { connections?: InboxMailConnection[]; connectEnabled?: boolean }
|
||||
}
|
||||
setMailConnections(json.data?.connections ?? [])
|
||||
// While Google's scope review keeps new consents withheld, the start
|
||||
// card must not send people to a settings page with no connect button.
|
||||
setMailConnectEnabled(json.data?.connectEnabled === true)
|
||||
} catch {
|
||||
// The extension may not be enabled at all; stay quiet.
|
||||
}
|
||||
@@ -1932,11 +1938,16 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
dense
|
||||
title={tStart('inbox_title')}
|
||||
body={tStart('inbox_body')}
|
||||
primary={{ label: tStart('inbox_primary'), href: '/settings/mail' }}
|
||||
secondary={{
|
||||
label: tStart('inbox_secondary'),
|
||||
onClick: () => fileInputRef.current?.click(),
|
||||
}}
|
||||
primary={
|
||||
mailConnectEnabled
|
||||
? { label: tStart('inbox_primary'), href: '/settings/mail' }
|
||||
: { label: tStart('inbox_secondary'), onClick: () => fileInputRef.current?.click() }
|
||||
}
|
||||
secondary={
|
||||
mailConnectEnabled
|
||||
? { label: tStart('inbox_secondary'), onClick: () => fileInputRef.current?.click() }
|
||||
: undefined
|
||||
}
|
||||
onDismiss={handleDismissOnboarding}
|
||||
dismissLabel={tStart('inbox_dismiss')}
|
||||
/>
|
||||
@@ -2111,11 +2122,16 @@ export default function InvoiceInboxWorkspace(_props: WorkspaceComponentProps) {
|
||||
floatIcons
|
||||
title={tStart('inbox_title')}
|
||||
body={tStart('inbox_body')}
|
||||
primary={{ label: tStart('inbox_primary'), href: '/settings/mail' }}
|
||||
secondary={{
|
||||
label: tStart('inbox_secondary'),
|
||||
onClick: () => fileInputRef.current?.click(),
|
||||
}}
|
||||
primary={
|
||||
mailConnectEnabled
|
||||
? { label: tStart('inbox_primary'), href: '/settings/mail' }
|
||||
: { label: tStart('inbox_secondary'), onClick: () => fileInputRef.current?.click() }
|
||||
}
|
||||
secondary={
|
||||
mailConnectEnabled
|
||||
? { label: tStart('inbox_secondary'), onClick: () => fileInputRef.current?.click() }
|
||||
: undefined
|
||||
}
|
||||
onDismiss={handleDismissOnboarding}
|
||||
dismissLabel={tStart('inbox_dismiss')}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user