Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/lib/helpers/oauth2-app-logo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from 'vitest';
import { resolveOAuth2AppLogoUrl } from './oauth2-app-logo';

describe('resolveOAuth2AppLogoUrl', () => {
it('uses the square Appwrite icon for the first-party CLI client', () => {
expect(
resolveOAuth2AppLogoUrl({
$id: 'appwrite-cli',
logoUri: 'https://example.com/wordmark.svg'
})
).toBe('/console/logos/appwrite-icon.svg');
});

it('keeps third-party logoUri values', () => {
expect(
resolveOAuth2AppLogoUrl({
$id: 'other-app',
logoUri: 'https://example.com/logo.png'
})
).toBe('https://example.com/logo.png');
});

it('returns an empty string when no logo is available', () => {
expect(resolveOAuth2AppLogoUrl({ $id: 'other-app', logoUri: ' ' })).toBe('');
expect(resolveOAuth2AppLogoUrl(null)).toBe('');
});
});
18 changes: 18 additions & 0 deletions src/lib/helpers/oauth2-app-logo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { base } from '$app/paths';

/**
* First-party clients may register a wide wordmark as `logoUri`. Consent and
* outcome avatars are square, so `object-fit: cover` crops those marks into
* unreadable fragments (see #3159 for Appwrite CLI). Prefer the square icon.
*/
const FIRST_PARTY_ICON_CLIENTS = new Set(['appwrite-cli']);

export function resolveOAuth2AppLogoUrl(
app: { $id?: string; logoUri?: string } | null | undefined
): string {
if (app?.$id && FIRST_PARTY_ICON_CLIENTS.has(app.$id)) {
return `${base}/logos/appwrite-icon.svg`;
}

return app?.logoUri?.trim() ?? '';
}
14 changes: 11 additions & 3 deletions src/routes/(public)/oauth2/consent-card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
type ResourcePage,
type ResourceNameMap
} from '$lib/helpers/oauth2-authorization-details';
import { resolveOAuth2AppLogoUrl } from '$lib/helpers/oauth2-app-logo';
import ResourceSelector from './resource-selector.svelte';

export type OAuth2Flow = 'authorization' | 'device';
Expand Down Expand Up @@ -110,6 +111,7 @@
);

const redirectHost = $derived(hostnameOf(grant.redirectUri));
const appLogoUrl = $derived(resolveOAuth2AppLogoUrl(app));
const appInitial = $derived((app.name || '?').charAt(0).toUpperCase());
const accountInitial = $derived((accountLabel || '?').charAt(0).toUpperCase());

Expand Down Expand Up @@ -493,8 +495,8 @@
<div class="consent">
<header class="header">
<div class="identity">
{#if app.logoUri}
<img src={app.logoUri} alt={app.name} class="avatar" />
{#if appLogoUrl}
<img src={appLogoUrl} alt={app.name} class="avatar" />
{:else}
<div class="avatar placeholder">{appInitial}</div>
{/if}
Expand Down Expand Up @@ -902,12 +904,18 @@
width: 3.5rem;
height: 3.5rem;
border-radius: var(--border-radius-l, 0.85rem);
object-fit: cover;
flex-shrink: 0;
border: 1px solid var(--border-neutral-strong);
background: var(--bgcolor-neutral-primary);
}

/* Keep wordmarks readable inside the square avatar (cover crops them). */
img.avatar {
object-fit: contain;
padding: 0.4rem;
box-sizing: border-box;
}

.avatar.placeholder {
display: flex;
align-items: center;
Expand Down
14 changes: 11 additions & 3 deletions src/routes/(public)/oauth2/outcome-card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { Card, Typography, Icon } from '@appwrite.io/pink-svelte';
import { IconCheck, IconX, IconLockClosed } from '@appwrite.io/pink-icons-svelte';
import { Button } from '$lib/elements/forms';
import { resolveOAuth2AppLogoUrl } from '$lib/helpers/oauth2-app-logo';
import type { OAuth2Flow, OAuth2Outcome } from './consent-card.svelte';

interface Props {
Expand All @@ -28,6 +29,7 @@

const approved = $derived(outcome === 'approved');
const appName = $derived(app?.name ?? 'the application');
const appLogoUrl = $derived(resolveOAuth2AppLogoUrl(app));
const appInitial = $derived((app?.name || '?').charAt(0).toUpperCase());
const accountInitial = $derived((accountLabel || '?').charAt(0).toUpperCase());

Expand All @@ -53,8 +55,8 @@
<div class="outcome" class:approved>
<header class="header">
<div class="identity">
{#if app?.logoUri}
<img src={app.logoUri} alt={appName} class="avatar" />
{#if appLogoUrl}
<img src={appLogoUrl} alt={appName} class="avatar" />
{:else}
<div class="avatar placeholder">{appInitial}</div>
{/if}
Expand Down Expand Up @@ -158,12 +160,18 @@
width: 3.5rem;
height: 3.5rem;
border-radius: var(--border-radius-l, 0.85rem);
object-fit: cover;
flex-shrink: 0;
border: 1px solid var(--border-neutral-strong);
background: var(--bgcolor-neutral-primary);
}

/* Keep wordmarks readable inside the square avatar (cover crops them). */
img.avatar {
object-fit: contain;
padding: 0.4rem;
box-sizing: border-box;
}

.avatar.placeholder {
display: flex;
align-items: center;
Expand Down
Loading