Repository navigation
Continue with Microsoft on the sign-in page #241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| export function MicrosoftIcon() { | ||
| return ( | ||
| <svg aria-hidden="true" viewBox="0 0 24 24" className="h-5 w-5"> | ||
| <path d="M2 2h9.5v9.5H2z" fill="#F25022" /> | ||
| <path d="M12.5 2H22v9.5h-9.5z" fill="#7FBA00" /> | ||
| <path d="M2 12.5h9.5V22H2z" fill="#00A4EF" /> | ||
| <path d="M12.5 12.5H22V22h-9.5z" fill="#FFB900" /> | ||
| </svg> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,8 +8,8 @@ import { | |
| } from '../../components/AgentToolLogos'; | ||
| import { Cursor, Grok, MuseColor } from '../../components/brand-icons'; | ||
| import { LogoIcon, LogoWordmark } from '../../components/SiteNav'; | ||
| import { authErrorMessage, googleSignInHref } from '../../lib/login'; | ||
| import { GoogleSignInButton } from './google-sign-in-button'; | ||
| import { authErrorMessage, signInHref } from '../../lib/login'; | ||
| import { ProviderSignInButton } from './provider-sign-in-button'; | ||
| import { RelayField } from './relay-field'; | ||
| import s from './login.module.css'; | ||
|
|
||
|
|
@@ -76,9 +76,13 @@ export default async function LoginPage({ searchParams }: LoginProps) { | |
| </p> | ||
| )} | ||
|
|
||
| <GoogleSignInButton | ||
| href={googleSignInHref({ next: first(params.next), invite })} | ||
| /> | ||
| {(['google', 'microsoft'] as const).map((provider) => ( | ||
| <ProviderSignInButton | ||
| key={provider} | ||
| provider={provider} | ||
| href={signInHref(provider, { next: first(params.next), invite })} | ||
|
Comment on lines
+79
to
+83
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Gate Microsoft sign-in on the Cloud route deployment.
|
||
| /> | ||
| ))} | ||
|
|
||
| <div className={s.worksWith}> | ||
| <p>Works with all your agents</p> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,38 @@ | ||
| import { teamsCloudUrl } from './teams-cloud'; | ||
|
|
||
| /** Messages for the `authError` codes Cloud's Google callback redirects with. */ | ||
| /** Messages for the `authError` codes Cloud's provider callbacks redirect with. */ | ||
| const AUTH_ERROR_MESSAGES: Readonly<Record<string, string>> = { | ||
| state: 'Your sign-in session expired. Please try again.', | ||
| google: 'We couldn’t complete Google sign-in. Please try again.', | ||
| microsoft: 'We couldn’t complete Microsoft sign-in. Please try again.', | ||
| schema: 'Sign-in is temporarily unavailable. Please try again shortly.', | ||
| unverified_email: | ||
| 'Google did not provide a verified email address. Use a Google account with a verified email and try again.', | ||
| 'Your account did not provide a verified email address. Use an account with a verified email and try again.', | ||
| }; | ||
|
|
||
| export function authErrorMessage(code: string | undefined): string | null { | ||
| return code && Object.hasOwn(AUTH_ERROR_MESSAGES, code) ? AUTH_ERROR_MESSAGES[code] : null; | ||
| } | ||
|
|
||
| export type SignInProvider = 'google' | 'microsoft'; | ||
|
|
||
| /** | ||
| * Cloud's Google start route owns validation: it normalizes `next` to a local | ||
| * Cloud's provider start route owns validation: it normalizes `next` to a local | ||
| * Cloud path and the invite token to its canonical form, so both pass through. | ||
| */ | ||
| export function googleSignInHref({ | ||
| next, | ||
| invite, | ||
| }: { | ||
| next?: string; | ||
| invite?: string; | ||
| }): string { | ||
| export function signInHref( | ||
| provider: SignInProvider, | ||
| { | ||
| next, | ||
| invite, | ||
| }: { | ||
| next?: string; | ||
| invite?: string; | ||
| }, | ||
| ): string { | ||
| const params = new URLSearchParams(); | ||
| if (next) params.set('next', next); | ||
| if (invite) params.set('invite_token', invite); | ||
| const query = params.toString(); | ||
| return teamsCloudUrl(`/api/auth/google/start${query ? `?${query}` : ''}`); | ||
| return teamsCloudUrl(`/api/auth/${provider}/start${query ? `?${query}` : ''}`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Share the in-flight guard between provider buttons.
Each
ProviderSignInButtonhas its ownpendingstate. If a user activates Google and then Microsoft before navigation completes, the Microsoft button still accepts the second click. Two authentication navigations can start. Use one in-flight guard for both links, and reset that guard on bfcache return.