diff --git a/packages/console/src/components/ImageWithErrorFallback/index.tsx b/packages/console/src/components/ImageWithErrorFallback/index.tsx index ba2c5df9e..30b6a8777 100644 --- a/packages/console/src/components/ImageWithErrorFallback/index.tsx +++ b/packages/console/src/components/ImageWithErrorFallback/index.tsx @@ -6,7 +6,12 @@ import FallbackImageDark from '@/assets/images/broken-image-dark.svg'; import FallbackImageLight from '@/assets/images/broken-image-light.svg'; import { useTheme } from '@/hooks/use-theme'; -const ImageWithErrorFallback = ({ src, alt, className }: ImgHTMLAttributes) => { +const ImageWithErrorFallback = ({ + src, + alt, + className, + ...props +}: ImgHTMLAttributes) => { const [hasError, setHasError] = useState(false); const theme = useTheme(); const Fallback = theme === AppearanceMode.LightMode ? FallbackImageLight : FallbackImageDark; @@ -19,7 +24,7 @@ const ImageWithErrorFallback = ({ src, alt, className }: ImgHTMLAttributes; } - return {alt}; + return {alt}; }; export default ImageWithErrorFallback; diff --git a/packages/console/src/hooks/use-current-user.ts b/packages/console/src/hooks/use-current-user.ts index 6f0807703..dcca99f5e 100644 --- a/packages/console/src/hooks/use-current-user.ts +++ b/packages/console/src/hooks/use-current-user.ts @@ -1,4 +1,4 @@ -import type { User } from '@logto/schemas'; +import type { UserProfileResponse } from '@logto/schemas'; import useSWR from 'swr'; import { adminTenantEndpoint, meApi } from '@/consts'; @@ -11,12 +11,12 @@ import useSwrFetcher from './use-swr-fetcher'; const useCurrentUser = () => { const userId = useLogtoUserId(); const api = useStaticApi({ prefixUrl: adminTenantEndpoint, resourceIndicator: meApi.indicator }); - const fetcher = useSwrFetcher(api); + const fetcher = useSwrFetcher(api); const { data: user, error, mutate, - } = useSWR(userId && `me/users/${userId}`, fetcher); + } = useSWR(userId && 'me', fetcher); const isLoading = !user && !error; diff --git a/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx b/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx index 7cb069976..b8747dfb8 100644 --- a/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx +++ b/packages/console/src/pages/Profile/components/BasicUserInfoSection/index.tsx @@ -1,4 +1,4 @@ -import type { User } from '@logto/schemas'; +import type { UserInfo } from '@logto/schemas'; import type { Nullable } from '@silverhand/essentials'; import { useState } from 'react'; @@ -12,7 +12,7 @@ import type { Row } from '../CardContent'; import CardContent from '../CardContent'; type Props = { - user: User; + user: UserInfo; onUpdate?: () => void; }; diff --git a/packages/console/src/pages/Profile/components/CardContent/index.tsx b/packages/console/src/pages/Profile/components/CardContent/index.tsx index 7d2904bc7..4f33ccc52 100644 --- a/packages/console/src/pages/Profile/components/CardContent/index.tsx +++ b/packages/console/src/pages/Profile/components/CardContent/index.tsx @@ -28,7 +28,7 @@ type Props = { data: Array>; }; -const CardContent = > | undefined>({ +const CardContent = > | undefined>({ title, data, }: Props) => { diff --git a/packages/console/src/pages/Profile/components/LinkAccountSection/index.tsx b/packages/console/src/pages/Profile/components/LinkAccountSection/index.tsx index 58577065b..098b21787 100644 --- a/packages/console/src/pages/Profile/components/LinkAccountSection/index.tsx +++ b/packages/console/src/pages/Profile/components/LinkAccountSection/index.tsx @@ -1,5 +1,5 @@ import { buildIdGenerator } from '@logto/core-kit'; -import type { ConnectorResponse, User } from '@logto/schemas'; +import type { ConnectorResponse, UserInfo } from '@logto/schemas'; import { AppearanceMode } from '@logto/schemas'; import type { Optional } from '@silverhand/essentials'; import { appendPath, conditional } from '@silverhand/essentials'; @@ -27,7 +27,7 @@ import NotSet from '../NotSet'; import * as styles from './index.module.scss'; type Props = { - user: User; + user: UserInfo; connectors?: ConnectorResponse[]; onUpdate: () => void; }; @@ -62,7 +62,7 @@ const LinkAccountSection = ({ user, connectors, onUpdate }: Props) => { return connectors.map(({ id, name, logo, logoDark, target }) => { const logoSrc = theme === AppearanceMode.DarkMode && logoDark ? logoDark : logo; - const relatedUserDetails = user.identities[target]?.details; + const relatedUserDetails = user.identities?.[target]?.details; const hasLinked = is(relatedUserDetails, socialUserInfoGuard); const conditionalUnlinkAction: Action[] = hasLinked ? [ diff --git a/packages/console/src/pages/Profile/containers/BasicUserInfoUpdateModal/index.tsx b/packages/console/src/pages/Profile/containers/BasicUserInfoUpdateModal/index.tsx index e8206fbf0..43a62dd18 100644 --- a/packages/console/src/pages/Profile/containers/BasicUserInfoUpdateModal/index.tsx +++ b/packages/console/src/pages/Profile/containers/BasicUserInfoUpdateModal/index.tsx @@ -72,7 +72,7 @@ const BasicUserInfoUpdateModal = ({ field, value: initialValue, isOpen, onClose const onSubmit = async () => { clearErrors(); void handleSubmit(async (data) => { - await api.patch(`me/user`, { json: { [field]: data[field] } }); + await api.patch('me', { json: { [field]: data[field] } }); toast.success(t('profile.updated', { target: t(`profile.settings.${field}`) })); onClose(); })(); diff --git a/packages/console/src/pages/Profile/containers/VerificationCodeModal/index.tsx b/packages/console/src/pages/Profile/containers/VerificationCodeModal/index.tsx index f71518eaf..96af71615 100644 --- a/packages/console/src/pages/Profile/containers/VerificationCodeModal/index.tsx +++ b/packages/console/src/pages/Profile/containers/VerificationCodeModal/index.tsx @@ -64,7 +64,7 @@ const VerificationCodeModal = () => { await api.post(`me/verification-codes/verify`, { json: { verificationCode, email } }); if (action === 'changeEmail') { - await api.patch(`me/user`, { json: { primaryEmail: email } }); + await api.patch('me', { json: { primaryEmail: email } }); toast.success(t('profile.email_changed')); onClose(); diff --git a/packages/console/src/pages/Profile/index.tsx b/packages/console/src/pages/Profile/index.tsx index 0f2041860..2bd9aeda2 100644 --- a/packages/console/src/pages/Profile/index.tsx +++ b/packages/console/src/pages/Profile/index.tsx @@ -61,12 +61,12 @@ const Profile = () => { { key: 'password', label: 'profile.password.password', - value: user.passwordEncrypted, + value: user.hasPassword, renderer: (value) => (value ? ******** : ), action: { name: 'profile.change', handler: () => { - navigate(user.passwordEncrypted ? 'verify-password' : 'change-password', { + navigate(user.hasPassword ? 'verify-password' : 'change-password', { state: { email: user.primaryEmail, action: 'changePassword' }, }); }, diff --git a/packages/core/src/routes-me/user.ts b/packages/core/src/routes-me/user.ts index cd022fa4d..5f646ecee 100644 --- a/packages/core/src/routes-me/user.ts +++ b/packages/core/src/routes-me/user.ts @@ -1,6 +1,7 @@ import { emailRegEx, passwordRegEx, usernameRegEx } from '@logto/core-kit'; +import type { UserProfileResponse } from '@logto/schemas'; import { userInfoSelectFields, arbitraryObjectGuard } from '@logto/schemas'; -import { pick } from '@silverhand/essentials'; +import { conditional, pick } from '@silverhand/essentials'; import { literal, object, string } from 'zod'; import RequestError from '#src/errors/RequestError/index.js'; @@ -25,26 +26,23 @@ export default function userRoutes( }, } = tenant; - router.get( - '/users/:userId', - koaGuard({ - params: object({ userId: string() }), - }), - async (ctx, next) => { - const { - params: { userId }, - } = ctx.guard; + router.get('/', async (ctx, next) => { + const { id: userId } = ctx.auth; - const user = await findUserById(userId); + const user = await findUserById(userId); - ctx.body = pick(user, ...userInfoSelectFields, 'passwordEncrypted'); + const responseData: UserProfileResponse = { + ...pick(user, ...userInfoSelectFields), + ...conditional(user.passwordEncrypted && { hasPassword: Boolean(user.passwordEncrypted) }), + }; - return next(); - } - ); + ctx.body = responseData; + + return next(); + }); router.patch( - '/user', + '/', koaGuard({ body: object({ username: string().regex(usernameRegEx), @@ -61,8 +59,9 @@ export default function userRoutes( assertThat(!user.isSuspended, new RequestError({ code: 'user.suspended', status: 401 })); await checkIdentifierCollision(body, userId); - await updateUserById(userId, body); - ctx.status = 204; + + const updatedUser = await updateUserById(userId, body); + ctx.body = pick(updatedUser, ...userInfoSelectFields); return next(); } @@ -71,6 +70,7 @@ export default function userRoutes( router.get('/custom-data', async (ctx, next) => { const { id: userId } = ctx.auth; const user = await findUserById(userId); + assertThat(!user.isSuspended, new RequestError({ code: 'user.suspended', status: 401 })); ctx.body = user.customData; @@ -87,13 +87,14 @@ export default function userRoutes( const { id: userId } = ctx.auth; const { body: customData } = ctx.guard; - await findUserById(userId); + const user = await findUserById(userId); + assertThat(!user.isSuspended, new RequestError({ code: 'user.suspended', status: 401 })); - const user = await updateUserById(userId, { + const updatedUser = await updateUserById(userId, { customData, }); - ctx.body = user.customData; + ctx.body = updatedUser.customData; return next(); } diff --git a/packages/core/src/routes-me/verification-code.ts b/packages/core/src/routes-me/verification-code.ts index a9c8e2e20..fbaa69797 100644 --- a/packages/core/src/routes-me/verification-code.ts +++ b/packages/core/src/routes-me/verification-code.ts @@ -28,7 +28,6 @@ export default function verificationCodeRoutes( '/verification-codes', koaGuard({ body: object({ email: string().regex(emailRegEx) }), - status: 204, }), async (ctx, next) => { const code = await createPasscode(undefined, codeType, ctx.guard.body); @@ -48,7 +47,6 @@ export default function verificationCodeRoutes( verificationCode: string().min(1), action: union([literal('changeEmail'), literal('changePassword')]), }), - status: 204, }), async (ctx, next) => { const { id: userId } = ctx.auth; diff --git a/packages/schemas/src/types/user.ts b/packages/schemas/src/types/user.ts index a416c4be0..d4b159aa5 100644 --- a/packages/schemas/src/types/user.ts +++ b/packages/schemas/src/types/user.ts @@ -20,7 +20,7 @@ export type UserInfo; -export type UserProfileResponse = UserInfo & { hasPasswordSet: boolean }; +export type UserProfileResponse = UserInfo & { hasPassword?: boolean }; export enum UserRole { Admin = 'admin',