On fix/restore-design: wip: pkce/favicon edits on restore-design (redundant; main has pkce)

This commit is contained in:
zooqueen
2026-06-22 21:55:03 -07:00
5 changed files with 56 additions and 2 deletions
+10
View File
@@ -25,6 +25,13 @@ export function Login({
const redirectUri = sp.get('redirect_uri') ?? undefined
const state = sp.get('state') ?? undefined
const clientIdOverride = sp.get('client_id') ?? undefined
// OAuth-authorize passthrough: when iam.hanzo.ai/login/oauth/authorize 302s a
// downstream app (console, chat, …) here, the original PKCE challenge + scope
// ride the query string. Forward them so the code IAM mints is bound to the
// app's verifier — hanzo.id is the login UI, IAM stays the OAuth backend.
const codeChallenge = sp.get('code_challenge') ?? undefined
const codeChallengeMethod = (sp.get('code_challenge_method') as 'S256' | 'plain' | null) ?? undefined
const scope = sp.get('scope') ?? undefined
const accent = brand.accentColor ?? '#ffffff'
const marketing = marketingFor(tenant.orgId)
const search = window.location.search
@@ -44,6 +51,9 @@ export function Login({
redirectUri={redirectUri}
state={state}
clientIdOverride={clientIdOverride ?? undefined}
codeChallenge={codeChallenge}
codeChallengeMethod={codeChallengeMethod}
scope={scope}
/>
<p className="hanzo-id-footer-links">
<a href="/forget">Forgot password?</a>
+13 -2
View File
@@ -63,8 +63,15 @@ export function createAuthClient(opts: AuthClientOptions): AuthClient {
url.searchParams.set('clientId', req.clientId)
url.searchParams.set('responseType', 'code')
if (req.redirectUri) url.searchParams.set('redirectUri', req.redirectUri)
url.searchParams.set('scope', 'openid profile email')
url.searchParams.set('scope', req.scope ?? 'openid profile email')
if (req.state) url.searchParams.set('state', req.state)
// PKCE passthrough: when this login completes a downstream app's OAuth
// authorize (console, chat, …), bind the app's challenge to the minted code
// so IAM enforces it at /oauth/token against the app's verifier.
if (req.codeChallenge) {
url.searchParams.set('code_challenge', req.codeChallenge)
url.searchParams.set('code_challenge_method', req.codeChallengeMethod ?? 'S256')
}
url.searchParams.set('type', type)
const res = await f(url.toString(), {
method: 'POST',
@@ -251,8 +258,12 @@ export function createAuthClient(opts: AuthClientOptions): AuthClient {
url.searchParams.set('clientId', req.clientId)
url.searchParams.set('responseType', 'code')
if (req.redirectUri) url.searchParams.set('redirectUri', req.redirectUri)
url.searchParams.set('scope', 'openid profile email')
url.searchParams.set('scope', req.scope ?? 'openid profile email')
if (req.state) url.searchParams.set('state', req.state)
if (req.codeChallenge) {
url.searchParams.set('code_challenge', req.codeChallenge)
url.searchParams.set('code_challenge_method', req.codeChallengeMethod ?? 'S256')
}
url.searchParams.set('type', type)
const isEmail = req.dest.includes('@')
const res = await f(url.toString(), {
+12
View File
@@ -6,6 +6,13 @@ export interface LoginRequest {
readonly organization: string
readonly redirectUri?: string
readonly state?: string
/** PKCE challenge to bind to the minted code, when this login completes an
* OAuth authorize on behalf of a downstream app (console, chat, …). The app
* holds the verifier and presents it at /oauth/token. Forwarded verbatim. */
readonly codeChallenge?: string
readonly codeChallengeMethod?: 'S256' | 'plain'
/** OAuth scope requested by the downstream app (defaults to openid profile email). */
readonly scope?: string
}
export interface LoginResponse {
@@ -86,6 +93,11 @@ export interface CodeLoginRequest {
readonly organization: string
readonly redirectUri?: string
readonly state?: string
/** PKCE challenge to bind to the minted code (downstream-app OAuth passthrough). */
readonly codeChallenge?: string
readonly codeChallengeMethod?: 'S256' | 'plain'
/** OAuth scope requested by the downstream app (defaults to openid profile email). */
readonly scope?: string
}
export interface TokenResponse {
+14
View File
@@ -9,6 +9,11 @@ export interface LoginFormProps {
readonly redirectUri?: string
readonly state?: string
readonly clientIdOverride?: string
/** PKCE challenge from a downstream app's OAuth authorize, forwarded to IAM. */
readonly codeChallenge?: string
readonly codeChallengeMethod?: 'S256' | 'plain'
/** OAuth scope from the downstream app's authorize request. */
readonly scope?: string
readonly onSuccess?: (res: LoginResponse) => void
readonly onMfaRequired?: (res: LoginResponse) => void
}
@@ -65,6 +70,9 @@ export function LoginForm(props: LoginFormProps) {
organization: client.tenant.orgId,
redirectUri: props.redirectUri,
state: props.state,
codeChallenge: props.codeChallenge,
codeChallengeMethod: props.codeChallengeMethod,
scope: props.scope,
})
handleResult(res)
} catch (err) {
@@ -106,6 +114,9 @@ export function LoginForm(props: LoginFormProps) {
organization: client.tenant.orgId,
redirectUri: props.redirectUri,
state: props.state,
codeChallenge: props.codeChallenge,
codeChallengeMethod: props.codeChallengeMethod,
scope: props.scope,
})
handleResult(res)
} catch (err) {
@@ -125,6 +136,9 @@ export function LoginForm(props: LoginFormProps) {
redirectUri={props.redirectUri}
state={props.state}
clientIdOverride={props.clientIdOverride}
codeChallenge={props.codeChallenge}
codeChallengeMethod={props.codeChallengeMethod}
scope={props.scope}
/>
) : null}
+7
View File
@@ -34,6 +34,10 @@ export interface ProviderButtonsProps {
readonly redirectUri?: string
readonly state?: string
readonly clientIdOverride?: string
/** PKCE challenge from a downstream app's OAuth authorize (social login passthrough). */
readonly codeChallenge?: string
readonly codeChallengeMethod?: 'S256' | 'plain'
readonly scope?: string
}
/**
@@ -60,6 +64,9 @@ export function ProviderButtons(props: ProviderButtonsProps) {
redirectUri,
state: props.state ?? mode,
provider: p.name,
scope: props.scope,
codeChallenge: props.codeChallenge,
codeChallengeMethod: props.codeChallengeMethod,
})
return (
<a