feat: add oauth callback email binding ui

This commit is contained in:
IanShaw027
2026-04-20 19:30:19 +08:00
parent 6a75bd77e3
commit 6ea3f42e2f
10 changed files with 916 additions and 36 deletions

View File

@@ -193,7 +193,7 @@ export interface OAuthTokenResponse {
token_type?: string
}
export interface PendingOAuthExchangeResponse extends Partial<OAuthTokenResponse> {
export interface PendingOAuthBindLoginResponse extends Partial<OAuthTokenResponse> {
redirect?: string
error?: string
adoption_required?: boolean
@@ -201,6 +201,10 @@ export interface PendingOAuthExchangeResponse extends Partial<OAuthTokenResponse
suggested_avatar_url?: string
}
export type PendingOAuthExchangeResponse = PendingOAuthBindLoginResponse
export interface PendingOAuthCreateAccountResponse extends OAuthTokenResponse {}
export type OAuthCompletionKind = 'login' | 'bind'
export interface OAuthAdoptionDecision {
@@ -235,6 +239,27 @@ export function getOAuthCompletionKind(
return isOAuthLoginCompletion(completion) ? 'login' : 'bind'
}
export function getPendingOAuthBindLoginKind(
completion: PendingOAuthBindLoginResponse
): OAuthCompletionKind {
return getOAuthCompletionKind(completion)
}
export function isPendingOAuthCreateAccountRequired(
completion: Pick<PendingOAuthBindLoginResponse, 'error'>
): boolean {
return completion.error === 'invitation_required'
}
export function hasPendingOAuthSuggestedProfile(
completion: Pick<
PendingOAuthBindLoginResponse,
'suggested_display_name' | 'suggested_avatar_url'
>
): boolean {
return Boolean(completion.suggested_display_name || completion.suggested_avatar_url)
}
export function persistOAuthTokenContext(tokens: Partial<OAuthTokenResponse>): void {
if (tokens.refresh_token) {
setRefreshToken(tokens.refresh_token)
@@ -431,11 +456,7 @@ export async function completeLinuxDoOAuthRegistration(
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<OAuthTokenResponse> {
const { data } = await apiClient.post<OAuthTokenResponse>('/auth/oauth/linuxdo/complete-registration', {
invitation_code: invitationCode,
...serializeOAuthAdoptionDecision(decision)
})
return data
return createPendingLinuxDoOAuthAccount(invitationCode, decision)
}
/**
@@ -447,32 +468,66 @@ export async function completeOIDCOAuthRegistration(
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<OAuthTokenResponse> {
const { data } = await apiClient.post<OAuthTokenResponse>('/auth/oauth/oidc/complete-registration', {
invitation_code: invitationCode,
...serializeOAuthAdoptionDecision(decision)
})
return data
return createPendingOIDCOAuthAccount(invitationCode, decision)
}
export async function completeWeChatOAuthRegistration(
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<OAuthTokenResponse> {
const { data } = await apiClient.post<OAuthTokenResponse>('/auth/oauth/wechat/complete-registration', {
invitation_code: invitationCode,
...serializeOAuthAdoptionDecision(decision)
})
return createPendingWeChatOAuthAccount(invitationCode, decision)
}
async function createPendingOAuthAccount(
provider: 'linuxdo' | 'oidc' | 'wechat',
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<PendingOAuthCreateAccountResponse> {
const { data } = await apiClient.post<PendingOAuthCreateAccountResponse>(
`/auth/oauth/${provider}/complete-registration`,
{
invitation_code: invitationCode,
...serializeOAuthAdoptionDecision(decision)
}
)
return data
}
export async function createPendingLinuxDoOAuthAccount(
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<PendingOAuthCreateAccountResponse> {
return createPendingOAuthAccount('linuxdo', invitationCode, decision)
}
export async function createPendingOIDCOAuthAccount(
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<PendingOAuthCreateAccountResponse> {
return createPendingOAuthAccount('oidc', invitationCode, decision)
}
export async function createPendingWeChatOAuthAccount(
invitationCode: string,
decision?: OAuthAdoptionDecision
): Promise<PendingOAuthCreateAccountResponse> {
return createPendingOAuthAccount('wechat', invitationCode, decision)
}
export async function completePendingOAuthBindLogin(
decision?: OAuthAdoptionDecision
): Promise<PendingOAuthBindLoginResponse> {
const { data } = await apiClient.post<PendingOAuthBindLoginResponse>(
'/auth/oauth/pending/exchange',
serializeOAuthAdoptionDecision(decision)
)
return data
}
export async function exchangePendingOAuthCompletion(
decision?: OAuthAdoptionDecision
): Promise<PendingOAuthExchangeResponse> {
const { data } = await apiClient.post<PendingOAuthExchangeResponse>(
'/auth/oauth/pending/exchange',
serializeOAuthAdoptionDecision(decision)
)
return data
return completePendingOAuthBindLogin(decision)
}
export const authAPI = {
@@ -498,6 +553,13 @@ export const authAPI = {
resetPassword,
refreshToken,
revokeAllSessions,
getPendingOAuthBindLoginKind,
isPendingOAuthCreateAccountRequired,
hasPendingOAuthSuggestedProfile,
completePendingOAuthBindLogin,
createPendingLinuxDoOAuthAccount,
createPendingOIDCOAuthAccount,
createPendingWeChatOAuthAccount,
exchangePendingOAuthCompletion,
completeLinuxDoOAuthRegistration,
completeOIDCOAuthRegistration,