Skip to content

Reset password flow #865

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

Merged
merged 1 commit into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions client/packages/lowcoder/src/api/userApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export interface ApiKeyPayload {
description?: string;
}

export interface ResetLostPasswordPayload {
token: string;
userEmail: string;
newPassword: string;
}

export interface FetchApiKeysResponse extends ApiResponse {
data: {
id: string;
Expand All @@ -69,6 +75,8 @@ class UserApi extends Api {
static markUserStatusURL = "/users/mark-status";
static userDetailURL = (id: string) => `/users/userDetail/${id}`;
static resetPasswordURL = `/users/reset-password`;
static forgotPasswordURL = `/users/lost-password`;
static resetLostPasswordURL = `/users/reset-lost-password`;
static fetchApiKeysURL = `/auth/api-keys`;
static createApiKeyURL = `/auth/api-key`;
static deleteApiKeyURL = (id: string) => `/auth/api-key/${id}`;
Expand Down Expand Up @@ -138,6 +146,15 @@ class UserApi extends Api {
return Api.post(UserApi.resetPasswordURL, { userId: userId });
}

static forgotPassword(userEmail: string): AxiosPromise<ApiResponse> {
return Api.post(UserApi.forgotPasswordURL, { userEmail });
}

static resetLostPassword(request: ResetLostPasswordPayload): AxiosPromise<ApiResponse> {
console.log(request);
return Api.post(UserApi.resetLostPasswordURL, request);
}

static createApiKey({
name,
description = ''
Expand Down
4 changes: 4 additions & 0 deletions client/packages/lowcoder/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
TRASH_URL,
USER_AUTH_URL,
ADMIN_APP_URL,
ORG_AUTH_FORGOT_PASSWORD_URL,
ORG_AUTH_RESET_PASSWORD_URL,
} from "constants/routesURL";

import React from "react";
Expand Down Expand Up @@ -173,6 +175,8 @@ class AppIndex extends React.Component<AppIndexProps, any> {
<LazyRoute path={USER_AUTH_URL} component={LazyUserAuthComp} />
<LazyRoute path={ORG_AUTH_LOGIN_URL} component={LazyUserAuthComp} />
<LazyRoute path={ORG_AUTH_REGISTER_URL} component={LazyUserAuthComp} />
<LazyRoute path={ORG_AUTH_FORGOT_PASSWORD_URL} component={LazyUserAuthComp} />
<LazyRoute path={ORG_AUTH_RESET_PASSWORD_URL} component={LazyUserAuthComp} />
<LazyRoute path={INVITE_LANDING_URL} component={LazyInviteLanding} />
<LazyRoute path={`${COMPONENT_DOC_URL}/:name`} component={LazyComponentDoc} />
<LazyRoute path={`/playground/:name/:dsl`} component={LazyComponentPlayground} />
Expand Down
10 changes: 10 additions & 0 deletions client/packages/lowcoder/src/constants/authConstants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {
AUTH_BIND_URL,
AUTH_FORGOT_PASSWORD_URL,
AUTH_LOGIN_URL,
AUTH_REGISTER_URL,
AUTH_RESET_PASSWORD_URL,
OAUTH_REDIRECT,
ORG_AUTH_FORGOT_PASSWORD_URL,
ORG_AUTH_LOGIN_URL,
ORG_AUTH_REGISTER_URL,
ORG_AUTH_RESET_PASSWORD_URL,
} from "constants/routesURL";
import { InviteInfo } from "api/inviteApi";
import Login, { ThirdPartyBindCard } from "pages/userAuth/login";
Expand All @@ -18,6 +22,8 @@ import {
KeyCloakLoginIcon,
EmailLoginIcon
} from "assets/icons";
import ForgotPassword from "pages/userAuth/forgotPassword";
import ResetPassword from "pages/userAuth/resetPassword";

export type AuthInviteInfo = InviteInfo & { invitationId: string };
export type AuthLocationState = { inviteInfo?: AuthInviteInfo; thirdPartyAuthError?: boolean };
Expand Down Expand Up @@ -85,9 +91,13 @@ export const AuthRoutes: Array<{ path: string; component: React.ComponentType<an
{ path: AUTH_LOGIN_URL, component: Login },
{ path: AUTH_BIND_URL, component: ThirdPartyBindCard },
{ path: AUTH_REGISTER_URL, component: UserRegister },
{ path: AUTH_FORGOT_PASSWORD_URL, component: ForgotPassword },
{ path: AUTH_RESET_PASSWORD_URL, component: ResetPassword },
{ path: OAUTH_REDIRECT, component: AuthRedirect },
{ path: ORG_AUTH_LOGIN_URL, component: Login },
{ path: ORG_AUTH_REGISTER_URL, component: UserRegister },
{ path: ORG_AUTH_FORGOT_PASSWORD_URL, component: ForgotPassword },
{ path: ORG_AUTH_RESET_PASSWORD_URL, component: ResetPassword },
];

export type ServerAuthType = "GOOGLE" | "GITHUB" | "FORM" | "KEYCLOAK" | "ORY" | "GENERIC";
Expand Down
4 changes: 4 additions & 0 deletions client/packages/lowcoder/src/constants/routesURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,17 @@ export const APP_EDITOR_URL = `${ALL_APPLICATIONS_URL}/:applicationId/:viewMode/
export const AUTH_BIND_URL = `${USER_AUTH_URL}/bind`;
export const AUTH_LOGIN_URL = `${USER_AUTH_URL}/login`;
export const AUTH_REGISTER_URL = `${USER_AUTH_URL}/register`;
export const AUTH_FORGOT_PASSWORD_URL = `${USER_AUTH_URL}/forgot-password`;
export const AUTH_RESET_PASSWORD_URL = `${USER_AUTH_URL}/lost-password`;
export const QR_CODE_OAUTH_URL = `${USER_AUTH_URL}/oauth/qrcode`;
export const OAUTH_REDIRECT = `${USER_AUTH_URL}/oauth/redirect`;
export const CAS_AUTH_REDIRECT = `${USER_AUTH_URL}/cas/redirect`;
export const LDAP_AUTH_LOGIN_URL = `${USER_AUTH_URL}/ldap/login`;
export const INVITE_LANDING_URL = "/invite/:invitationId";
export const ORG_AUTH_LOGIN_URL = `/org/:orgId/auth/login`;
export const ORG_AUTH_REGISTER_URL = `/org/:orgId/auth/register`;
export const ORG_AUTH_FORGOT_PASSWORD_URL = `/org/:orgId/auth/forgot-password`;
export const ORG_AUTH_RESET_PASSWORD_URL = `/org/:orgId/auth/lost-password`;
export const MARKETPLACE_TYPE_URL = `${MARKETPLACE_URL}/:marketplaceType`;

export const APPLICATION_VIEW_URL = (appId: string, viewMode: AppViewMode) =>
Expand Down
9 changes: 7 additions & 2 deletions client/packages/lowcoder/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2810,8 +2810,12 @@ export const en = {
"userAuth": {
"registerByEmail": "Sign Up",
"email": "Email:",
"inputEmail": "Please Enter Your Email",
"inputValidEmail": "Please Enter a Valid Email",
"inputEmail": "Please enter your email",
"inputValidEmail": "Please enter a valid email",
"forgotPassword": "Forgot Password",
"forgotPasswordInfo": "Enter your email and we'll send you a link to reset your password.",
"forgotPasswordSuccess": "Please check your email for reset password link.",
"forgotPasswordError": "Something went wrong. Please try again.",
"register": "Sign Up",
"userLogin": "Sign In",
"login": "Sign In",
Expand All @@ -2836,6 +2840,7 @@ export const en = {
"resetPasswordDesc": "Reset User {name}'s Password. A New Password Will Be Generated After Reset.",
"resetSuccess": "Reset Succeeded",
"resetSuccessDesc": "Password Reset Succeeded. The New Password is: {password}",
"resetLostPasswordSuccess": "Password Reset Succeeded. Please login again.",
"copyPassword": "Copy Password",
"poweredByLowcoder": "Powered by: Lowcoder.cloud"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ export function HomeLayout(props: HomeLayoutProps) {
const resList: HomeRes[] = displayElements
.filter((e) =>
searchValue
? e.name.toLocaleLowerCase().includes(searchValue) ||
e.createBy.toLocaleLowerCase().includes(searchValue)
? e.name.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase()) ||
e.createBy.toLocaleLowerCase().includes(searchValue.toLocaleLowerCase())
: true
)
.filter((e) => {
Expand Down
101 changes: 101 additions & 0 deletions client/packages/lowcoder/src/pages/userAuth/forgotPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useContext, useState, useMemo } from "react";
import {
AuthContainer,
ConfirmButton,
FormWrapperMobile,
StyledRouteLinkLogin,
} from "pages/userAuth/authComponents";
import { FormInput, PasswordInput, messageInstance } from "lowcoder-design";
import { AUTH_LOGIN_URL, ORG_AUTH_LOGIN_URL } from "constants/routesURL";
import UserApi from "api/userApi";
import { useRedirectUrl } from "util/hooks";
import { checkEmailValid } from "util/stringUtils";
import styled from "styled-components";
import { requiresUnAuth } from "./authHOC";
import { useLocation } from "react-router-dom";
import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants";
import { trans } from "i18n";
import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils";
import { useParams } from "react-router-dom";
import { Divider } from "antd";
import { validateResponse } from "api/apiUtils";

const StyledFormInput = styled(FormInput)`
margin-bottom: 16px;
`;

const RegisterContent = styled(FormWrapperMobile)`
display: flex;
flex-direction: column;
margin-bottom: 0px;
`;

function ForgotPassword() {
const [account, setAccount] = useState("");
const [loading, setLoading] = useState(false);
const location = useLocation();

const orgId = useParams<any>().orgId;

const onSubmit = () => {
setLoading(true);
UserApi.forgotPassword(account)
.then((resp) => {
// TODO: need proper response from BE
// if (validateResponse(resp)) {
// messageInstance.success(trans("userAuth.forgotPasswordSuccess"));
// }
if (resp.status === 200) {
messageInstance.success(trans("userAuth.forgotPasswordSuccess"));
}
})
.catch((e) => {
messageInstance.error(trans("userAuth.forgotPasswordError"));
})
.finally(() => {
setLoading(false);
})
}

const forgotPasswordHeading = trans("userAuth.forgotPassword")
const subHeading = trans("userAuth.poweredByLowcoder");

return (
<AuthContainer
heading={forgotPasswordHeading}
subHeading={subHeading}
type="large"
>
<p style={{textAlign: 'center'}}>{trans("userAuth.forgotPasswordInfo")}</p>
<RegisterContent>
<StyledFormInput
className="form-input"
label={''}
onChange={(value, valid) => setAccount(valid ? value : "")}
placeholder={trans("userAuth.inputEmail")}
checkRule={{
check: checkEmailValid,
errorMsg: trans("userAuth.inputValidEmail"),
}}
/>
<ConfirmButton
disabled={!account}
onClick={onSubmit}
loading={loading}
>
{trans("button.submit")}
</ConfirmButton>
</RegisterContent>
<Divider/>
<StyledRouteLinkLogin to={{
pathname: orgId
? ORG_AUTH_LOGIN_URL.replace(':orgId', orgId)
: AUTH_LOGIN_URL,
state: location.state
}}>{trans("userAuth.userLogin")}
</StyledRouteLinkLogin>
</AuthContainer>
);
}

export default requiresUnAuth(ForgotPassword);
24 changes: 20 additions & 4 deletions client/packages/lowcoder/src/pages/userAuth/formLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants";
import { trans } from "i18n";
import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils";
import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth";
import { AUTH_REGISTER_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL";
import { useLocation, useParams } from "react-router-dom";
import { AUTH_FORGOT_PASSWORD_URL, AUTH_REGISTER_URL, ORG_AUTH_FORGOT_PASSWORD_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL";
import { Link, useLocation, useParams } from "react-router-dom";
import { Divider } from "antd";
import Flex from "antd/es/flex";

const AccountLoginWrapper = styled(FormWrapperMobile)`
display: flex;
flex-direction: column;
margin-bottom: 40px;
margin-bottom: 0px;

.form-input.password-input {
margin-bottom: 0px;
}
`;

type FormLoginProps = {
Expand Down Expand Up @@ -70,10 +75,21 @@ export default function FormLogin(props: FormLoginProps) {
}}
/>
<PasswordInput
className="form-input"
className="form-input password-input"
onChange={(value) => setPassword(value)}
valueCheck={() => [true, ""]}
/>
<Flex justify="end" style={{margin: '10px 0'}}>
<Link to={{
pathname: orgId
? ORG_AUTH_FORGOT_PASSWORD_URL.replace(':orgId', orgId)
: AUTH_FORGOT_PASSWORD_URL,
state: location.state
}}
>
{`${trans("userAuth.forgotPassword")}?`}
</Link>
</Flex>
<ConfirmButton loading={loading} disabled={!account || !password} onClick={onSubmit}>
{trans("userAuth.login")}
</ConfirmButton>
Expand Down
2 changes: 1 addition & 1 deletion client/packages/lowcoder/src/pages/userAuth/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const StyledPasswordInput = styled(PasswordInput)`
const RegisterContent = styled(FormWrapperMobile)`
display: flex;
flex-direction: column;
margin-bottom: 40px;
margin-bottom: 0px;
`;

function UserRegister() {
Expand Down
Loading
Loading