0

我正在尝试按照此设置指南使用 Firebase 注册具有多因素身份验证的用户:https ://cloud.google.com/identity-platform/docs/web/mfa

我正在努力弄清楚如何让我的函数在将代码发送到用户的手机后等待用户输入的验证码(我认为这就是代码出错的原因。)我下面的当前代码片段将在我之后抛出这个错误单击发送验证码按钮:错误:'auth/missing-verification-code',消息:'电话身份验证凭据是使用空的 SMS 验证码创建的。

这是我第一次实施 MFA 流程,所以有人对我应该如何做有想法吗?谢谢!

import React, { Component } from 'react'
import { store } from 'react-notifications-component';
import { Grid, Row, Col } from 'react-flexbox-grid';
import { withRouter } from 'react-router-dom';
import { Form, Formik } from 'formik';

import { NOTIFICATION } from '../../../utils/constants.js';
import { firestore, firebase } from "../../../Fire.js";
import { updateProfileSchema, updateProfilePhoneSchema, checkVCodeSchema } from "../../../utils/formSchemas"
import { Hr, Recaptcha, Wrapper } from '../../../utils/styles/misc.js';
import { FField } from '../../../utils/styles/forms.js';
import { H1, Label, RedText, H2, LLink, GreenHoverText, SmText } from '../../../utils/styles/text.js';
import { MdGreenToInvBtn, MdInvToPrimaryBtn } from '../../../utils/styles/buttons.js';

class AdminProfile extends Component {
    constructor(props) {
        super(props)
    
        this.state = {
             user: "",
             codeSent: false,
             editingPhone: false,
             vCode: "",
             loading: {
                user: true
             }
        }
    }

    componentDidMount(){
        this.unsubscribeUser = firestore.collection("users").doc(this.props.user.uid)
            .onSnapshot((doc) => {
                if(doc.exists){
                    let docWithMore = Object.assign({}, doc.data());
                    docWithMore.id = doc.id;
                    this.setState({
                        user: docWithMore,
                        loading: {
                            user: false
                        }
                    })
                } else {
                    console.error("User doesn't exist.")
                }
            });
    }
  
    componentWillUnmount() {
        if(this.unsubscribeUser){
            this.unsubscribeUser();
        }
    }

    sendVerificationCode = (values) => {
        store.addNotification({
            title: "reCAPTCHA",
            message: `Please complete the reCAPTCHA below to continue.`,
            type: "success",
            ...NOTIFICATION
          })
        window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha', {
            'callback': (response) => {
                this.props.user.multiFactor.getSession().then((multiFactorSession) => {
                    // Specify the phone number and pass the MFA session.
                    let phoneInfoOptions = {
                        phoneNumber: values.phone,
                        session: multiFactorSession
                    };
                    let phoneAuthProvider = new firebase.auth.PhoneAuthProvider();
                    // Send SMS verification code.
                    return phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, window.recaptchaVerifier);
                    
                }).then(async (verificationId) => {
                    this.setState({
                        codeSent: true
                    })
                    
                    // Ask user for the verification code.
                    // TODO: how to do this async? do I need to split up my requests?
                    // let code = await this.getAttemptedCode()
                    let cred = firebase.auth.PhoneAuthProvider.credential(verificationId, this.state.vCode);
                    let multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred);
                    // Complete enrollment.
                    this.props.user.multiFactor.multiFactor.enroll(multiFactorAssertion, this.props.user.userName);
                }).catch((error) => {
                    console.error("Error adding multi-factor authentication: ", error);
                    store.addNotification({
                      title: "Error",
                      message: `Error adding multi-factor authentication: ${error}`,
                      type: "danger",
                      ...NOTIFICATION
                    })
                    window.recaptchaVerifier.clear()
                });;

            },
            'expired-callback': () => {
              // Response expired. Ask user to solve reCAPTCHA again.
              store.addNotification({
                  title: "Timeout",
                  message: `Please solve the reCAPTCHA again.`,
                  type: "danger",
                  ...NOTIFICATION
                })
              window.recaptchaVerifier.clear()
            }
           });
           
           window.recaptchaVerifier.render()
    }

    getAttemptedCode = async () => {
        
    }

    render() {
        if(this.state.loading.user){
            return (
                <Wrapper>
                    <H2>Loading...</H2>
                </Wrapper>
            )
        } else {
            return (
                <Wrapper>
                    <LLink to={`/admin/dashboard`}> 
                        <MdInvToPrimaryBtn type="button">
                            <i className="fas fa-chevron-left" />&nbsp; Return to admin dashboard
                        </MdInvToPrimaryBtn>
                    </LLink>
                    <H1>Admin Profile</H1>
                    <Formik
                            initialValues={{
                                firstName: this.state.user.firstName,
                                lastName: this.state.user.lastName,
                                email: this.state.user.email,
                                phone: this.state.user.phone
                            }}
                            enableReinitialize={true}
                            validationSchema={updateProfileSchema}
                            onSubmit={(values, actions) => {
                                //this.updateProfile(values);
                                actions.resetForm();
                            }}
                        >
                            {props => (
                            <Form>
                                <Grid fluid>
                          
                                    <Row>
                                        <Col xs={12}>
                                            <Label htmlFor="phone">Phone: </Label>
                                            <SmText><RedText> <GreenHoverText onClick={() => this.setState({ editingPhone: true })}>update phone</GreenHoverText></RedText></SmText>
                                            <FField
                                                type="phone"
                                                disabled={true}
                                                onChange={props.handleChange}
                                                name="phone"
                                                value={props.values.phone}
                                                placeholder="(123) 456-7890"
                                            />
                                        </Col>
                                    </Row>
                                    <Row center="xs">
                                        <Col xs={12}>
                                            <MdGreenToInvBtn type="submit" disabled={!props.dirty && !props.isSubmitting}>
                                                Update
                                            </MdGreenToInvBtn>
                                        </Col>
                                    </Row>
                                </Grid>
                            </Form>
                            )}
                        </Formik>
                        
                        {this.state.editingPhone && (
                            <>
                            <Hr/>
                            <Formik
                                initialValues={{
                                    phone: this.state.user.phone
                                }}
                                enableReinitialize={true}
                                validationSchema={updateProfilePhoneSchema}
                                onSubmit={(values, actions) => {
                                    this.sendVerificationCode(values);
                                }}
                            >
                                {props => (
                                <Form>
                                    <Grid fluid>
                                        <Row>
                                            <Col xs={12} sm={6}>
                                                <Label htmlFor="phone">Phone: </Label>
                                                <FField
                                                    type="phone"
                                                    onChange={props.handleChange}
                                                    name="phone"
                                                    value={props.values.phone}
                                                    placeholder="(123) 456-7890"
                                                />
                                                {props.errors.phone && props.touched.phone ? (
                                                    <RedText>{props.errors.phone}</RedText>
                                                ) : (
                                                    ""
                                                )}
                                            </Col>
                                        </Row>
                                        <Row center="xs">
                                            <Col xs={12}>
                                                <MdGreenToInvBtn type="submit" disabled={!props.dirty && !props.isSubmitting}>
                                                    Send verification code
                                                </MdGreenToInvBtn>
                                            </Col>
                                        </Row>
                                        
                            
                                    </Grid>
                                </Form>
                                )}
                            </Formik>
                            </>
                        )}

                        {this.state.codeSent && (
                            <>
                            <Formik
                                initialValues={{
                                    vCode: ""
                                }}
                                enableReinitialize={true}
                                validationSchema={checkVCodeSchema}
                                onSubmit={(values, actions) => {
                                    this.SetState({ vCode: values.vCode });
                                }}
                            >
                                {props => (
                                <Form>
                                    <Grid fluid>
                                        <Row>
                                            <FField
                                                type="text"
                                                onChange={props.handleChange}
                                                name="vCode"
                                                value={props.values.vCode}
                                                placeholder="abc123"
                                            />
                                            {props.errors.vCode && props.touched.vCode ? (
                                                <RedText>{props.errors.vCode}</RedText>
                                            ) : (
                                                ""
                                            )}
                                        </Row>
                                        <Row center="xs">
                                            <Col xs={12}>
                                                {/* TODO: add send code again button? */}
                                                <MdGreenToInvBtn type="submit" disabled={!props.dirty && !props.isSubmitting}>
                                                    Submit verification code
                                                </MdGreenToInvBtn>
                                            </Col>
                                        </Row>
                                        
                            
                                    </Grid>
                                </Form>
                                )}
                            </Formik>
                            </>
                        )}
                        
                        <Recaptcha id="recaptcha" />
                        
                </Wrapper>
            )
        }
    }
}

export default withRouter(AdminProfile);
4

1 回答 1

0

弄清楚了!我错误地认为verificationId传回的verifyPhoneNumber()是原始代码,我不想将其保存在客户端的本地状态中,因为我认为这是一个安全漏洞。幸运的verificationId是,这不是要输入的原始代码,而是 JWT 或抽象的东西,所以我只是将该值保存在 React 状态中,然后由一个单独的函数引用,该函数getAttemptedCode(values)仅在用户单击提交后调用尝试的代码。

如果有人发现我发现此方法存在安全漏洞,请告诉我!

I'll add my code block when I am done building the component fully!
于 2021-10-14T00:12:56.610 回答