oauth: add gdocs and gchat providers (#11447)

* oauth: add gdocs and gchat providers, gchat icon

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* oauth: Google sign-in button for every accounts.google.com provider, least-privilege gchat default

- AppConnectInner: derive isGoogleSignin from the registry auth_url instead of a
  hardcoded list, so gdocs/gchat/gforms/gcloud/gworkspace get Google's button.
- gchat default scopes: chat.messages is a restricted scope; default to
  chat.spaces.readonly + chat.messages.create (both sensitive). chat.messages /
  chat.messages.readonly stay selectable.
- BRAND_COLORS.md: GchatIcon row in sort order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-10-01 10:42:30 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a8e65ab1e6
commit bba58c4167
6 changed files with 108 additions and 5 deletions
+33
View File
@@ -98,6 +98,39 @@
"prompt": "consent"
}
},
"gdocs": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": ["https://www.googleapis.com/auth/documents"],
"scope_options": [
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/documents.readonly",
"https://www.googleapis.com/auth/drive.file"
],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
}
},
"gchat": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"scopes": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.messages.create"
],
"scope_options": [
"https://www.googleapis.com/auth/chat.spaces.readonly",
"https://www.googleapis.com/auth/chat.messages",
"https://www.googleapis.com/auth/chat.messages.readonly",
"https://www.googleapis.com/auth/chat.messages.create",
"https://www.googleapis.com/auth/chat.memberships.readonly"
],
"extra_params": {
"access_type": "offline",
"prompt": "consent"
}
},
"gcloud": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
@@ -449,16 +449,15 @@
// Google's terms require its own button on the control that starts the sign-in, which is
// the step-2 Connect: step 1 only picks a type, and a manual step 2 saves a resource
// without ever reaching Google.
// without ever reaching Google. Every registry provider that signs in through Google's
// authorize endpoint counts; `google` itself is a login provider, not a registry entry.
run(() => {
isGoogleSignin =
step == 2 &&
!manual &&
(resourceType == 'google' ||
resourceType == 'gmail' ||
resourceType == 'gcal' ||
resourceType == 'gdrive' ||
resourceType == 'gsheets')
(registryEntryFor(resourceType)?.auth_url?.startsWith('https://accounts.google.com/') ??
false))
})
run(() => {
@@ -80,6 +80,8 @@
'gmail',
'gcal',
'gforms',
'gdocs',
'gchat',
'gcloud',
'gworkspace',
'basecamp',
@@ -137,6 +137,7 @@ repeatedly — check the brand's own page.
| `FunkwhaleIcon` | `funkwhale` | mixed | #009FE3 | #009FE3 | 10.69 | 5.71 | www.funkwhale.audio/logos (theme/images/icon.svg) |
| `GSheetsIcon` | `gsheets` | fixed | #009954 | #009954 | 3.57 | 12.47 | Google product logo sheets_2026q3 (gstatic productlogos, used on workspace.google.com/products/sheets) |
| `GcalIcon` | `gcal` | fixed | #BBE2FF | #BBE2FF | 3.40 | 12.47 | Google's own Calendar 2026 product logo, https://www.gstatic.com/images/branding/productlogos/calendar_2026/v2/web/192px.svg (paths verbatim) |
| `GchatIcon` | `gchat` | fixed | #0EBC5F | #0EBC5F | 2.80 | 4.98 | Google's own Chat 2026 product icon, https://www.gstatic.com/images/branding/productlogos/chat_2026/v2/web/192px.svg (paths verbatim, ids namespaced) |
| `GdocsIcon` | `gdocs` | inherits | #718096 | #A9B0BA | 3.88 | 5.71 | Google's own Docs product icon (gstatic.com/images/branding/productlogos/docs_2026/v2/web/192px.svg, served on workspace.google.com/products/docs) |
| `GdriveIcon` | `gdrive` | fixed | #B43333 | #B43333 | 5.87 | 10.05 | https://www.gstatic.com/images/branding/productlogos/drive_2026/v2/web/192px.svg, Google's own product-logo CDN; paths and gradient stops are verbatim |
| `GhostCmsIcon` | `ghostcms` | pair | #15171A | #FFFFFF | 17.38 | 12.47 | docs.ghost.org |
@@ -0,0 +1,65 @@
<script lang="ts">
interface Props {
height?: string
width?: string
}
let { height = '24px', width = '24px' }: Props = $props()
</script>
<!-- Google's own Chat product icon, gstatic.com/images/branding/productlogos/chat_2026/v2/web/192px.svg.
Google forbids recolouring its logos, so this ships unmodified apart from namespacing the
gradient/mask ids, which are document-global. -->
<svg
{width}
{height}
viewBox="0 0 192 192"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-label="Google Chat"
>
<rect width="160" height="96" x="16" y="28" fill="#00AF57" rx="48" />
<path
fill="#0EBC5F"
d="M133 48c28.167 0 51 22.834 51 51 0 28.167-22.833 51-51 51H96.624l-34.857 23.064c-3.86 2.544-5.789 3.816-7.372 3.92a6 6 0 0 1-5.612-3.022C48 172.583 48 170.271 48 165.649V148.81C25.121 143.78 8 123.39 8 99c0-28.166 22.834-51 51-51h74z"
/>
<mask
id="gchat-mask-a"
width="176"
height="129"
x="8"
y="48"
maskUnits="userSpaceOnUse"
style="mask-type:alpha"
>
<path
fill="#0EBC5F"
d="M133 48c28.167 0 51 22.834 51 51 0 28.167-22.833 51-51 51H96.722l-39.428 25.896c-3.99 2.62-9.294-.242-9.294-5.015V148.81C25.121 143.78 8 123.39 8 99c0-28.166 22.834-51 51-51h74z"
/>
</mask>
<g mask="url(#gchat-mask-a)">
<rect width="160" height="96" x="16" y="28" fill="#0EBC5F" rx="48" />
<rect width="160" height="96" x="16" y="28" fill="url(#gchat-paint-b)" rx="48" />
<path
stroke="#fff"
stroke-linecap="round"
stroke-width="12"
d="M62 94s8.84 18 34 18 34-17.182 34-17.182"
/>
</g>
<defs>
<linearGradient
id="gchat-paint-b"
x1="96"
x2="96"
y1="28"
y2="124"
gradientUnits="userSpaceOnUse"
>
<stop offset=".09" stop-color="#94D4FF" />
<stop offset=".28" stop-color="#78C9FF" />
<stop offset=".88" stop-color="#01AE58" stop-opacity="0" />
</linearGradient>
</defs>
</svg>
@@ -33,6 +33,7 @@ import StripeIcon from './StripeIcon.svelte'
import TelegramIcon from './TelegramIcon.svelte'
import FunkwhaleIcon from './FunkwhaleIcon.svelte'
import GdocsIcon from './GdocsIcon.svelte'
import GchatIcon from './GchatIcon.svelte'
import NextcloudIcon from './NextcloudIcon.svelte'
import NetsuiteIcon from './NetsuiteIcon.svelte'
import FaunadbIcon from './FaunadbIcon.svelte'
@@ -386,6 +387,7 @@ export const APP_TO_ICON_COMPONENT = {
helper: WindmillIcon,
windmillhub: WindmillIcon,
gdocs: GdocsIcon,
gchat: GchatIcon,
ocs: NextcloudIcon,
faunadb: FaunadbIcon,
clickhouse: ClickhouseIcon,
@@ -715,6 +717,7 @@ export {
DatadogIcon,
FunkwhaleIcon,
GdocsIcon,
GchatIcon,
FaunadbIcon,
ClickhouseIcon,
OpenaiIcon,