Skip to content
This repository was archived by the owner on Sep 21, 2026. It is now read-only.

Commit 964c9b3

Browse files
authored
Merge pull request #3161 from appwrite/fix-3159-cli-oauth-logo
fix: show Appwrite icon for CLI OAuth consent avatars
2 parents e96cf47 + bc7855a commit 964c9b3

4 files changed

Lines changed: 67 additions & 6 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { resolveOAuth2AppLogoUrl } from './oauth2-app-logo';
3+
4+
describe('resolveOAuth2AppLogoUrl', () => {
5+
it('uses the square Appwrite icon for the first-party CLI client', () => {
6+
expect(
7+
resolveOAuth2AppLogoUrl({
8+
$id: 'appwrite-cli',
9+
logoUri: 'https://example.com/wordmark.svg'
10+
})
11+
).toBe('/console/logos/appwrite-icon.svg');
12+
});
13+
14+
it('keeps third-party logoUri values', () => {
15+
expect(
16+
resolveOAuth2AppLogoUrl({
17+
$id: 'other-app',
18+
logoUri: 'https://example.com/logo.png'
19+
})
20+
).toBe('https://example.com/logo.png');
21+
});
22+
23+
it('returns an empty string when no logo is available', () => {
24+
expect(resolveOAuth2AppLogoUrl({ $id: 'other-app', logoUri: ' ' })).toBe('');
25+
expect(resolveOAuth2AppLogoUrl(null)).toBe('');
26+
});
27+
});

‎src/lib/helpers/oauth2-app-logo.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { base } from '$app/paths';
2+
3+
/**
4+
* First-party clients may register a wide wordmark as `logoUri`. Consent and
5+
* outcome avatars are square, so `object-fit: cover` crops those marks into
6+
* unreadable fragments (see #3159 for Appwrite CLI). Prefer the square icon.
7+
*/
8+
const FIRST_PARTY_ICON_CLIENTS = new Set(['appwrite-cli']);
9+
10+
export function resolveOAuth2AppLogoUrl(
11+
app: { $id?: string; logoUri?: string } | null | undefined
12+
): string {
13+
if (app?.$id && FIRST_PARTY_ICON_CLIENTS.has(app.$id)) {
14+
return `${base}/logos/appwrite-icon.svg`;
15+
}
16+
17+
return app?.logoUri?.trim() ?? '';
18+
}

‎src/routes/(public)/oauth2/consent-card.svelte‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
type ResourcePage,
4949
type ResourceNameMap
5050
} from '$lib/helpers/oauth2-authorization-details';
51+
import { resolveOAuth2AppLogoUrl } from '$lib/helpers/oauth2-app-logo';
5152
import ResourceSelector from './resource-selector.svelte';
5253
5354
export type OAuth2Flow = 'authorization' | 'device';
@@ -110,6 +111,7 @@
110111
);
111112
112113
const redirectHost = $derived(hostnameOf(grant.redirectUri));
114+
const appLogoUrl = $derived(resolveOAuth2AppLogoUrl(app));
113115
const appInitial = $derived((app.name || '?').charAt(0).toUpperCase());
114116
const accountInitial = $derived((accountLabel || '?').charAt(0).toUpperCase());
115117
@@ -493,8 +495,8 @@
493495
<div class="consent">
494496
<header class="header">
495497
<div class="identity">
496-
{#if app.logoUri}
497-
<img src={app.logoUri} alt={app.name} class="avatar" />
498+
{#if appLogoUrl}
499+
<img src={appLogoUrl} alt={app.name} class="avatar" />
498500
{:else}
499501
<div class="avatar placeholder">{appInitial}</div>
500502
{/if}
@@ -902,12 +904,18 @@
902904
width: 3.5rem;
903905
height: 3.5rem;
904906
border-radius: var(--border-radius-l, 0.85rem);
905-
object-fit: cover;
906907
flex-shrink: 0;
907908
border: 1px solid var(--border-neutral-strong);
908909
background: var(--bgcolor-neutral-primary);
909910
}
910911
912+
/* Keep wordmarks readable inside the square avatar (cover crops them). */
913+
img.avatar {
914+
object-fit: contain;
915+
padding: 0.4rem;
916+
box-sizing: border-box;
917+
}
918+
911919
.avatar.placeholder {
912920
display: flex;
913921
align-items: center;

‎src/routes/(public)/oauth2/outcome-card.svelte‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Card, Typography, Icon } from '@appwrite.io/pink-svelte';
44
import { IconCheck, IconX, IconLockClosed } from '@appwrite.io/pink-icons-svelte';
55
import { Button } from '$lib/elements/forms';
6+
import { resolveOAuth2AppLogoUrl } from '$lib/helpers/oauth2-app-logo';
67
import type { OAuth2Flow, OAuth2Outcome } from './consent-card.svelte';
78
89
interface Props {
@@ -28,6 +29,7 @@
2829
2930
const approved = $derived(outcome === 'approved');
3031
const appName = $derived(app?.name ?? 'the application');
32+
const appLogoUrl = $derived(resolveOAuth2AppLogoUrl(app));
3133
const appInitial = $derived((app?.name || '?').charAt(0).toUpperCase());
3234
const accountInitial = $derived((accountLabel || '?').charAt(0).toUpperCase());
3335
@@ -53,8 +55,8 @@
5355
<div class="outcome" class:approved>
5456
<header class="header">
5557
<div class="identity">
56-
{#if app?.logoUri}
57-
<img src={app.logoUri} alt={appName} class="avatar" />
58+
{#if appLogoUrl}
59+
<img src={appLogoUrl} alt={appName} class="avatar" />
5860
{:else}
5961
<div class="avatar placeholder">{appInitial}</div>
6062
{/if}
@@ -158,12 +160,18 @@
158160
width: 3.5rem;
159161
height: 3.5rem;
160162
border-radius: var(--border-radius-l, 0.85rem);
161-
object-fit: cover;
162163
flex-shrink: 0;
163164
border: 1px solid var(--border-neutral-strong);
164165
background: var(--bgcolor-neutral-primary);
165166
}
166167
168+
/* Keep wordmarks readable inside the square avatar (cover crops them). */
169+
img.avatar {
170+
object-fit: contain;
171+
padding: 0.4rem;
172+
box-sizing: border-box;
173+
}
174+
167175
.avatar.placeholder {
168176
display: flex;
169177
align-items: center;

0 commit comments

Comments
 (0)