Compare commits
4 Commits
codex/refa
...
codex/refa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
005f84d55f | ||
|
|
6184b2f291 | ||
|
|
5e382844f8 | ||
|
|
ea27125433 |
@@ -49,11 +49,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/__tests__/embedded-user-id-auth.test.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"web/__tests__/embedded-user-id-store.test.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -146,14 +141,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx": {
|
||||
"jsx_a11y/tabindex-no-positive": {
|
||||
"count": 3
|
||||
},
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/(shareLayout)/webapp-signin/normalForm.tsx": {
|
||||
"jsx_a11y/click-events-have-key-events": {
|
||||
"count": 2
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import CheckCode from '@/app/(shareLayout)/webapp-signin/check-code/page'
|
||||
import MailAndCodeAuth from '@/app/(shareLayout)/webapp-signin/components/mail-and-code-auth'
|
||||
@@ -22,12 +23,12 @@ const mockStoreState = {
|
||||
shareCode: 'test-app',
|
||||
}
|
||||
|
||||
const useWebAppStoreMock = vi.fn((selector?: (state: typeof mockStoreState) => any) => {
|
||||
const useWebAppStoreMock = vi.fn((selector?: (state: typeof mockStoreState) => unknown) => {
|
||||
return selector ? selector(mockStoreState) : mockStoreState
|
||||
})
|
||||
|
||||
vi.mock('@/context/web-app-context', () => ({
|
||||
useWebAppStore: (selector?: (state: typeof mockStoreState) => any) =>
|
||||
useWebAppStore: (selector?: (state: typeof mockStoreState) => unknown) =>
|
||||
useWebAppStoreMock(selector),
|
||||
}))
|
||||
|
||||
@@ -36,23 +37,23 @@ const webAppEmailLoginWithCodeMock = vi.fn()
|
||||
const sendWebAppEMailLoginCodeMock = vi.fn()
|
||||
|
||||
vi.mock('@/service/common', () => ({
|
||||
webAppLogin: (...args: any[]) => webAppLoginMock(...args),
|
||||
webAppEmailLoginWithCode: (...args: any[]) => webAppEmailLoginWithCodeMock(...args),
|
||||
sendWebAppEMailLoginCode: (...args: any[]) => sendWebAppEMailLoginCodeMock(...args),
|
||||
webAppLogin: (...args: unknown[]) => webAppLoginMock(...args),
|
||||
webAppEmailLoginWithCode: (...args: unknown[]) => webAppEmailLoginWithCodeMock(...args),
|
||||
sendWebAppEMailLoginCode: (...args: unknown[]) => sendWebAppEMailLoginCodeMock(...args),
|
||||
}))
|
||||
|
||||
const fetchAccessTokenMock = vi.fn()
|
||||
|
||||
vi.mock('@/service/share', () => ({
|
||||
fetchAccessToken: (...args: any[]) => fetchAccessTokenMock(...args),
|
||||
fetchAccessToken: (...args: unknown[]) => fetchAccessTokenMock(...args),
|
||||
}))
|
||||
|
||||
const setWebAppAccessTokenMock = vi.fn()
|
||||
const setWebAppPassportMock = vi.fn()
|
||||
|
||||
vi.mock('@/service/webapp-auth', () => ({
|
||||
setWebAppAccessToken: (...args: any[]) => setWebAppAccessTokenMock(...args),
|
||||
setWebAppPassport: (...args: any[]) => setWebAppPassportMock(...args),
|
||||
setWebAppAccessToken: (...args: unknown[]) => setWebAppAccessTokenMock(...args),
|
||||
setWebAppPassport: (...args: unknown[]) => setWebAppPassportMock(...args),
|
||||
webAppLogout: vi.fn(),
|
||||
}))
|
||||
|
||||
@@ -71,6 +72,7 @@ beforeEach(() => {
|
||||
|
||||
describe('embedded user id propagation in authentication flows', () => {
|
||||
it('passes embedded user id when logging in with email and password', async () => {
|
||||
const user = userEvent.setup()
|
||||
const params = new URLSearchParams()
|
||||
params.set('redirect_url', encodeURIComponent('/chatbot/test-app'))
|
||||
useSearchParamsMock.mockReturnValue(params)
|
||||
@@ -80,13 +82,21 @@ describe('embedded user id propagation in authentication flows', () => {
|
||||
|
||||
render(<MailAndPasswordAuth isEmailSetup />)
|
||||
|
||||
fireEvent.change(screen.getByLabelText('login.email'), {
|
||||
target: { value: 'user@example.com' },
|
||||
})
|
||||
fireEvent.change(screen.getByLabelText(/login\.password/), {
|
||||
target: { value: 'strong-password' },
|
||||
})
|
||||
fireEvent.click(screen.getByRole('button', { name: 'login.signBtn' }))
|
||||
const emailInput = screen.getByLabelText('login.email')
|
||||
const passwordInput = screen.getByLabelText(/login\.password/)
|
||||
const submitButton = screen.getByRole('button', { name: 'login.signBtn' })
|
||||
|
||||
expect(emailInput).toHaveAttribute('name', 'email')
|
||||
expect(emailInput).toHaveAttribute('autocomplete', 'email')
|
||||
expect(emailInput).toHaveProperty('tabIndex', 0)
|
||||
expect(passwordInput).toHaveAttribute('name', 'password')
|
||||
expect(passwordInput).toHaveAttribute('autocomplete', 'current-password')
|
||||
expect(passwordInput).toHaveProperty('tabIndex', 0)
|
||||
expect(submitButton).toHaveAttribute('type', 'submit')
|
||||
expect(submitButton).toHaveProperty('tabIndex', 0)
|
||||
|
||||
await user.type(emailInput, 'user@example.com')
|
||||
await user.type(passwordInput, 'strong-password{Enter}')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(fetchAccessTokenMock).toHaveBeenCalledWith({
|
||||
@@ -97,6 +107,25 @@ describe('embedded user id propagation in authentication flows', () => {
|
||||
expect(setWebAppAccessTokenMock).toHaveBeenCalledWith('login-token')
|
||||
expect(setWebAppPassportMock).toHaveBeenCalledWith('test-app', 'passport-token')
|
||||
expect(replaceMock).toHaveBeenCalledWith('/chatbot/test-app')
|
||||
expect(webAppLoginMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('exposes a named password visibility action', async () => {
|
||||
const user = userEvent.setup()
|
||||
const params = new URLSearchParams()
|
||||
params.set('redirect_url', encodeURIComponent('/chatbot/test-app'))
|
||||
useSearchParamsMock.mockReturnValue(params)
|
||||
|
||||
render(<MailAndPasswordAuth isEmailSetup />)
|
||||
|
||||
const passwordInput = screen.getByLabelText('login.password')
|
||||
expect(passwordInput).toHaveAttribute('type', 'password')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'login.showPassword' }))
|
||||
expect(passwordInput).toHaveAttribute('type', 'text')
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'login.hidePassword' }))
|
||||
expect(passwordInput).toHaveAttribute('type', 'password')
|
||||
})
|
||||
|
||||
it('does not call password login services when the redirect target is external', async () => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { resolveWebAppLoginRedirect } from '@/app/(shareLayout)/webapp-signin/login-redirect'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { emailRegex } from '@/config'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
@@ -101,29 +101,33 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={noop}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="my-2 system-md-semibold text-text-secondary">
|
||||
<Form
|
||||
onFormSubmit={() => {
|
||||
void handleEmailPasswordLogin()
|
||||
}}
|
||||
>
|
||||
<Field name="email" className="mb-3 block">
|
||||
<FieldLabel className="my-2 py-0 system-md-semibold text-text-secondary">
|
||||
{t(($) => $.email, { ns: 'login' })}
|
||||
</label>
|
||||
</FieldLabel>
|
||||
<div className="mt-1">
|
||||
<Input
|
||||
<FieldControl
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onValueChange={setEmail}
|
||||
id="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
spellCheck={false}
|
||||
placeholder={t(($) => $.emailPlaceholder, { ns: 'login' }) || ''}
|
||||
tabIndex={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="password" className="my-2 flex items-center justify-between">
|
||||
<span className="system-md-semibold text-text-secondary">
|
||||
<Field name="password" className="mb-3 block">
|
||||
<div className="my-2 flex items-center justify-between">
|
||||
<FieldLabel className="py-0 system-md-semibold text-text-secondary">
|
||||
{t(($) => $.password, { ns: 'login' })}
|
||||
</span>
|
||||
</FieldLabel>
|
||||
<Link
|
||||
href={`/webapp-reset-password?${searchParams.toString()}`}
|
||||
className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'pointer-events-none text-components-button-secondary-accent-text-disabled'}`}
|
||||
@@ -132,39 +136,48 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
||||
>
|
||||
{t(($) => $.forget, { ns: 'login' })}
|
||||
</Link>
|
||||
</label>
|
||||
</div>
|
||||
<div className="relative mt-1">
|
||||
<Input
|
||||
<FieldControl
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onValueChange={setPassword}
|
||||
id="password"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') handleEmailPasswordLogin()
|
||||
}}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
spellCheck={false}
|
||||
placeholder={t(($) => $.passwordPlaceholder, { ns: 'login' }) || ''}
|
||||
tabIndex={2}
|
||||
className="pr-10"
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center">
|
||||
<Button type="button" variant="ghost" onClick={() => setShowPassword(!showPassword)}>
|
||||
{showPassword ? '👀' : '😝'}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
aria-label={t(($) => $[showPassword ? 'hidePassword' : 'showPassword'], {
|
||||
ns: 'login',
|
||||
})}
|
||||
className="mr-1 size-8 p-0 text-text-tertiary hover:text-text-secondary"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<span className="i-ri-eye-off-line size-4" aria-hidden="true" />
|
||||
) : (
|
||||
<span className="i-ri-eye-line size-4" aria-hidden="true" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
|
||||
<div className="mb-2">
|
||||
<Button
|
||||
tabIndex={2}
|
||||
type="submit"
|
||||
variant="primary"
|
||||
onClick={handleEmailPasswordLogin}
|
||||
disabled={isLoading || !email || !password}
|
||||
className="w-full"
|
||||
>
|
||||
{t(($) => $.signBtn, { ns: 'login' })}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user