import React from 'react'; import { Input, Button, Layout, Form, Checkbox, message } from 'antd'; import { UserOutlined, LockOutlined, MailOutlined, QuestionCircleOutlined, RightCircleOutlined, LeftCircleOutlined } from '@ant-design/icons'; import { Ellipsis } from 'react-spinners-css'; const { Content } = Layout class Login extends React.Component { constructor(props) { super(props); this.state = { failedLogin: false, errorFetch: false, login: true, register: false, loading: false, forgotPass: false, forgotPassReset: false, forgotPassUsername: "", forgotPassResetLoading: false, code: "", needVerify: false, verifyEmail: "", verifyEmailLoading: false }; } componentDidMount() { let index = window.location.pathname.indexOf("/reset/password/") if (index !== -1) { this.setState({ forgotPassReset: true, login: false, forgotPassResetLoading: true }) const pathSplit = window.location.pathname.slice(index, window.location.pathname.length).split("/") if (pathSplit.length < 4) message.error("Invalid link. Please check that you have entered the link correctly.") else this.checkResetPasswordLink(pathSplit[3], pathSplit[4]) } else { index = window.location.pathname.indexOf("/verify/") if (index !== -1) { this.setState({ needVerify: true, verifyEmailLoading: true, login: false }) const pathSplit = window.location.pathname.slice(index, window.location.pathname.length).split("/") if (pathSplit.length < 4) message.error("Invalid link. Please check that you have entered the link correctly.") else this.handleEmailVerify(pathSplit[2], pathSplit[3]) } } } handleRegister = values => { this.setState({ loading: true }) fetch(window.ipAddress + "/v1/account/create", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "username": values.username, "password": values.password, "email": values.email }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then((data) => { //console.log(data) if (data.success === true) { message.success({ content: "Woohoo! Successfully registered, you can now login via the login screen!" }) } else if (data.error === "email-verify") { message.success("Woohoo! Successfully registered!") message.info("We now require you to verify your email " + data.emailVerify + " before being able to log into the platform", 10) this.setState({ needVerify: true, verifyEmail: data.emailVerify, register: false }) } else if (data.error === "email-taken") { message.warn({ content: "Oops. Email already taken" }) } else if (data.error === "username-taken") { message.warn({ content: "Oops. Username already taken" }) } else if (data.error === "registration-disabled") { message.error("Oops. Registration is currently disabled, please contact an administrator for help.") } else if (data.error === "email-formatting") { message.error({ content: "Oops. Your email has not been registered for Sieberrsec CTF yet, please register using the form." }) } else { message.error({ content: "Oops. Unknown error" }) } this.setState({ loading: false }) }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }); this.setState({ loading: false }) }) } handleLogin = values => { this.setState({ loading: true }) fetch(window.ipAddress + "/v1/account/login", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "username": values.username, "password": values.password, }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then(async (data) => { //console.log(data) if (data.success === true) { await this.props.handleLogin(data.token, data.permissions, values.remember) } else { if (data.error === "wrong-details") { message.error({ content: "Oops. Your username/email or password was incorrect." }) } else if (data.error === "need-verify") { message.info("Please verify your email in order to login to the platform.", 10) this.setState({ login: false, needVerify: true, verifyEmail: data.emailVerify }) } else if (data.error === "login-disabled") { message.error({ content: "Oops. Login is disabled for non-admin users." }) } else { message.error({ content: "Oops. Unknown error" }) } } this.setState({ loading: false }) }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }) this.setState({ loading: false }) }) } handleForgot = values => { this.setState({ loading: true }) fetch(window.ipAddress + "/v1/account/forgot/pass", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "email": values.email }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then(async (data) => { //console.log(data) if (data.success === true) { message.success("Request sent successfully.") } else { if (data.error === "disabled") message.error({ content: "Oops. It seems like the forgot password function is disabled." }) else message.error({ content: "Oops. Unknown error" }) } this.setState({ loading: false }) }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }) this.setState({ loading: false }) }) } checkResetPasswordLink = async (username, code) => { await fetch(window.ipAddress + "/v1/account/forgot/check", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "code": code, "username": username }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then((data) => { //console.log(data) if (data.success === true) { this.setState({ forgotPassUsername: username, code: code }) } else { if (data.error === "invalid-code") { message.error("Oops. It seems like the link might have expired.") } else if (data.error === "disabled") message.error({ content: "Oops. It seems like the forgot password function is disabled." }) else message.error({ content: "Oops. Unknown error" }) this.setState({ forgotPassReset: false, login: true }) } }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }); }) this.setState({ forgotPassResetLoading: false }) window.history.pushState({}, "", "/"); } handleResetPasword = async (values) => { this.setState({ loading: true }) await fetch(window.ipAddress + "/v1/account/forgot/reset", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "code": this.state.code, "username": this.state.forgotPassUsername, "password": values.password }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then((data) => { //console.log(data) if (data.success === true) { message.success("Password successfully changed. You can now login using the new password.") this.setState({ login: true, forgotPassReset: false }) } else { if (data.error === "invalid-code") { message.error("Oops. It seems like the link might have expired.") } else if (data.error === "disabled") message.error({ content: "Oops. It seems like the forgot password function is disabled." }) else message.error({ content: "Oops. Unknown error" }) } }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }); }) this.setState({ loading: false }) } handleEmailVerify = async (username, code) => { this.setState({ loading: true, verifyEmailLoading: true }) await fetch(window.ipAddress + "/v1/account/verify", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "username": username, "code": code }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then( async (data) => { //console.log(data) if (data.success === true) { message.success("Email successfully verified!") await this.props.handleLogin(data.token, data.permissions, true) } else { if (data.error === "disabled") { message.error("Oops. It seems like the email verification function is disabled.", 5) message.info("You may simply proceed to login without verifying your email.", 5) this.setState({ needVerify: false, login: true }) } else if (data.error === "login-disabled") { message.success("Email successfully verified!") message.warn("Login is currently disabled for non-admin users so we couldn't log you in automatically.") this.setState({ needVerify: false, login: true }) } else if (data.error === "already-verified") { message.info("This email is already verified. You may proceed to login", 5) this.setState({ needVerify: false, login: true }) } else if (data.error === "invalid-code") { message.error("Oops. Invalid email verification code") this.setState({ needVerify: false, login: true }) } else message.error({ content: "Oops. Unknown error" }) } }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }); }) this.setState({ loading: false, verifyEmailLoading: false }) window.history.pushState({}, "", "/"); } handleResendVerification = async (email) => { this.setState({ loading: true }) await fetch(window.ipAddress + "/v1/account/verify/resend", { method: 'post', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ "email": email, }) }).then((results) => { return results.json(); //return data in JSON (since its JSON data) }).then((data) => { //console.log(data) if (data.success === true) { message.success("Email verification link resent!") } else { if (data.error === "disabled") { message.error("Oops. It seems like the email verification function is disabled.", 5) message.info("You may simply proceed to login without verifying your email.", 5) this.setState({ needVerify: false, login: true }) } else if (data.error === "already-verified") { message.info("This email is already verified. You may proceed to login", 5) this.setState({ needVerify: false, login: true }) } else if (data.error === "too-recent") { message.error("Oops. It seems like an email verifcation email was just sent", 5) message.info("You will be able to send another email in " + data.waitTime, 5) } else message.error({ content: "Oops. Unknown error" }) } }).catch((error) => { console.log(error) message.error({ content: "Oops. There was an issue connecting to the server" }); }) this.setState({ loading: false }) } render() { return ( <Layout style={{ maxWidth: "100vw", maxHeight: "100vh" }}> <Content style={{ display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "rgba(0, 0, 0, 0)", backgroundImage: "url(" + require("./../assets/mainBG.webp").default + ")" }}> <div className="login-banner login-banner-responsive"> <div style={{ fontSize: "7ch", color: "#595959" }}> <span style={{ fontWeight: "500", textShadow: '1px -1px 1px -1px #000000' }}>Sieberrsec Training Platform</span> </div> <div style={{ color: "#595959", fontSize: "5ch" }}> <p style={{ textShadow: '1px 1px 1px 1px #000000' }}>The Wheel. Reinvented.™</p> </div> </div> <div className="login-page login-page-responsive"> <div style={{ padding: "15px", marginBottom: "5vh" }}> <img src={require("./../assets/sieberrsec_ctf.svg").default} style={{ width: "100%" }}></img> </div> {this.state.login && ( <div style={{ width: "98%" }}> <h1 style={{ color: "white", fontSize: "3ch" }}>Sign In</h1> <Form name="normal_login" className="login-form" initialValues={{ remember: true }} onFinish={this.handleLogin} style={{ width: "95%" }} > <Form.Item name="username" rules={[{ required: true, message: 'Please enter your username/email' }]} > <Input allowClear prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Username/Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please enter your password.' }]} > <Input prefix={<LockOutlined className="site-form-item-icon" />} type="password" placeholder="Password" allowClear /> </Form.Item> <Form.Item > <div style={{ display: "flex", justifyContent: "space-between" }}> <Form.Item name="remember" valuePropName="checked" noStyle> <Checkbox>Remember me</Checkbox> </Form.Item> <a href="#" id="forgot-password" onClick={() => { this.setState({ login: false, forgotPass: true }) }}><b>I forgot my password <QuestionCircleOutlined /></b></a> </div> </Form.Item> <Form.Item> <div style={{ display: "flex", alignItems: "center" }}> <Button type="primary" htmlType="submit" className="login-form-button" style={{ marginRight: "2ch" }} loading={this.state.loading}>Log in</Button> <span>Or <a href="#" id="register-toggle" onClick={() => { this.setState({ login: false, register: true }) }} ><b>Register now <RightCircleOutlined /></b></a></span> </div> </Form.Item> </Form> </div> )} {this.state.register && ( <div style={{ width: "98%" }}> <h1 style={{ color: "white", fontSize: "3ch" }}>Register an Account</h1> <Form name="register_form" id="register-form" className="register-form" onFinish={this.handleRegister} style={{ width: "95%" }} requiredMark="optional" > <Form.Item name="username" rules={[{ required: true, message: 'Please enter a username' }, { message: "Please enter an alphanumeric username (without spaces) <= 50 characters.", pattern: /^[a-zA-Z0-9_]{1,50}$/ }]} > <Input id="register-username" allowClear prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Enter a new username" /> </Form.Item> <Form.Item name="email" rules={[{ required: true, message: 'Please enter an email' }, { type: 'email', message: "Please enter a valid email", }]} > <Input id="register-email" allowClear prefix={<MailOutlined />} placeholder="Enter a new email" /> </Form.Item> <Form.Item name="password" rules={[ { required: true, message: 'Please input your password!', }, { message: "Please enter a password that is >= 1 character and <= 200 characters", pattern: /^.{1,200}$/ } ]} hasFeedback > <Input.Password allowClear prefix={<LockOutlined />} placeholder="Enter a new password" /> </Form.Item> <Form.Item name="confirm" dependencies={['password']} hasFeedback rules={[ { required: true, message: 'Please confirm your password!', }, ({ getFieldValue }) => ({ validator(rule, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject('Oops, the 2 passwords do not match'); }, }), ]} > <Input.Password allowClear prefix={<LockOutlined />} placeholder="Confirm new password" /> </Form.Item> <Form.Item> <Button loading={this.state.loading} type="primary" htmlType="submit" className="login-form-button" style={{ marginBottom: "1.5vh" }}>Register</Button> <p>Already have an account? <a href="#" onClick={() => { this.setState({ login: true, register: false }) }}><b>Login Here <LeftCircleOutlined /></b></a></p> </Form.Item> </Form> </div> )} {this.state.forgotPass && ( <div style={{ width: "98%" }}> <h1 style={{ color: "white", fontSize: "3ch" }}>Forgot Password</h1> <Form onFinish={this.handleForgot} style={{ width: "95%" }} > <Form.Item name="email" rules={[{ required: true, message: 'Please enter your email' }, { type: "email", message: "Please enter a valid email" }]} > <Input allowClear prefix={<MailOutlined />} placeholder="Email" /> </Form.Item> <p> If an account associated with the email above exists, you will receive a password reset email in your inbox <br /><br /> Please note that there is a limit on how often password reset emails can be requested per user. A new email will <b>not be sent if you have just requested for one</b>. </p> <Form.Item> <div style={{ display: "flex", alignItems: "center", marginTop: "4ch" }}> <Button type="primary" htmlType="submit" style={{ marginRight: "2ch" }} loading={this.state.loading}>Send Email</Button> <span>Or <a href="#" onClick={() => { this.setState({ login: true, forgotPass: false }) }} ><b>Remember your password? <LeftCircleOutlined /></b></a></span> </div> </Form.Item> </Form> </div> )} {this.state.forgotPassReset && ( <div style={{ width: "98%" }}> {this.state.forgotPassResetLoading ? ( <div style={{ display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column" }}> <h1>Loading Link Details</h1> <Ellipsis color="#177ddc" size={120} /> </div> ) : ( <div> <h1 style={{ color: "white", fontSize: "3ch" }}>Reset Password</h1> <h4>Resetting password for <u>{this.state.forgotPassUsername}</u></h4> <Form onFinish={this.handleResetPasword} style={{ width: "95%" }} > <Form.Item name="password" rules={[ { required: true, message: 'Please input your password', }, ]} hasFeedback > <Input.Password allowClear prefix={<LockOutlined />} placeholder="Enter a new password" /> </Form.Item> <Form.Item name="confirm" dependencies={['password']} hasFeedback rules={[ { required: true, message: 'Please confirm your password', }, ({ getFieldValue }) => ({ validator(rule, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject('Oops, the 2 passwords do not match'); }, }), ]} > <Input.Password allowClear prefix={<LockOutlined />} placeholder="Confirm new password" /> </Form.Item> <Form.Item> <div style={{ display: "flex", alignItems: "center", marginTop: "4ch" }}> <Button type="primary" htmlType="submit" style={{ marginRight: "2ch" }} loading={this.state.loading}>Reset Password</Button> <span>Or <a href="#" onClick={() => { this.setState({ login: true, forgotPass: false, forgotPassReset: false }) }} ><b>Remember your password? <LeftCircleOutlined /></b></a></span> </div> </Form.Item> </Form> </div> )} </div> )} {this.state.needVerify && ( <div style={{ width: "98%" }}> {this.state.verifyEmailLoading ? ( <div style={{ display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column" }}> <h1>Verifying Email</h1> <Ellipsis color="#177ddc" size={120} /> </div> ) : ( <div > <h1 style={{ color: "white", fontSize: "3ch" }}>Email Verification Required</h1> <p> Hi, we require you to <b>verify your email (<code>{this.state.verifyEmail}</code>)</b> in order to login to the platform. Please check your inbox and click on the verification link sent to you. <br /><br /> Did not receive an email? You can resend a verficiation email below! <br /> Please note that there is a limit on how often verification emails can be sent per user. </p> <Button type="primary" icon={<MailOutlined />} loading={this.state.loading} onClick={() => { this.handleResendVerification(this.state.verifyEmail) }}>Resend Verification</Button> </div> )} </div> )} </div> </Content> </Layout> ); } } export default Login;