1

I've got a view and a component. I'm trying to do auth here.

As a user, I input username and password, click login. This emits the information to the parent component, which makes a fetch request to API gateway in AWS. This fetch response has a header X-Session-Id that I'm interested in.

I've got the emit bit working fine.

However, I'm unable to pass the header value back to the component, and I'm unable to set new_password_required to true, which would add a new input field for a new password, as well as replace the login button with a reset password button.

I feel like I need to do this with props, but I'm unable to successfully pass the values from parent to child.

Also, should the reset password bit have its own component?

Here's my code below. This is my first frontend, so I'm not familiar with how I am supposed to share it (e.g. with a playground). Also, I'm trying to stick to vanilla vue for now since I'm learning (I've only get vue-router installed I think)

parent:

<template>
  <div id="app" class="small-container">
    <login-form @login:user="loginUser($event)" />
  </div>
</template>

<script>
import LoginForm from "@/components/LoginForm.vue";

export default {
  name: "Login",
  components: {
    LoginForm
  },
  data() {
    return {
      session_id: String,
      new_password_required: Boolean
    };
  },
  methods: {
    async loginUser(loginEvent) {
      try {
        const response = await fetch(
          process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/login/user",
          {
            method: "POST",
            body: JSON.stringify(loginEvent)
          }
        );
        const data = await response.json();
        console.log(data);
        if (data.headers["X-Session-Id"] != null) {
          this.session_id = data.headers["X-Session-Id"];
          this.new_password_required = true;
        }
      } catch (error) {
        console.error(error);
      }
    },
    async resetPassword(resetPasswordEvent) {
      try {
        const response = await fetch(
          process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/reset/user/password",
          {
            method: "POST",
            body: JSON.stringify(resetPasswordEvent)
          }
        );
        const data = await response.json();
        console.log(data);
      } catch (error) {
        console.error(error);
      }
    }
  }
};
</script>

Component:

<template>
  <div id="login-form">
    <h1>Serverless App</h1>
    <form>
      <label for="email_address">Email Address:</label><br />
      <input
        v-model="login_details.email_address"
        type="text"
        id="email_address"
        name="email_address"
      /><br />
      <label for="password">Password:</label><br />
      <input
        v-model="login_details.password"
        type="password"
        id="password"
        name="password"
      />
      <label v-if="new_password_required" for="new_password"
        >New Password:</label
      ><br />
      <input
        v-if="new_password_required"
        v-model="login_details.new_password"
        type="password"
        id="new_password"
        name="new_password"
      />
    </form>
    <button v-if="!new_password_required" @click="loginUser($event)">
      Login
    </button>
    <button v-if="new_password_required" @click="resetPassword($event)">
      Reset Password
    </button>
  </div>
</template>

<script>
export default {
  name: "LoginForm",
  props: {
    session_id: String,
    new_password_required: {
      type: Boolean,
      default: () => false
    }
  },
  data() {
    return {
      login_details: {
        email_address: "",
        password: "",
        new_password: ""
      }
    };
  },

  methods: {
    loginUser() {
      console.log("testing loginUser...");
      const loginEvent = {
        email_address: this.login_details.email_address,
        password: this.login_details.password
      };
      this.$emit("login:user", loginEvent);
    },

    resetPassword() {
      console.log("testing resetPassword...");
      const resetPasswordEvent = {
        email_address: this.login_details.email_address,
        password: this.login_details.password,
        new_password: this.login_details.new_password,
        session_id: this.login_details.sessionId
      };
      this.$emit("reset:Password", resetPasswordEvent);
    }
  }
};
</script>
5
  • Can you include why and how you intent to use X-Session-Id inside the child component? Commented Jan 18, 2021 at 6:24
  • of course. I'm using amazon cognito for auth. Logging in uses the Cognito InitiateAuth() method to see the user's status. On initial login, the status will be FORCE_CHANGE_PASSWORD. If no password change is needed, the user gets a bearer token. When a password needs to be reset, another request is made to AdminRespondToAuthChallenge() method, which takes X-Session-Id as a parameter. This parameter is sent to api gateway via the resetPassword() method in the parent Commented Jan 18, 2021 at 7:01
  • If you add a ref attribute to your child you can access the instance method from the parent by this.$refs.childRefName.resetPassword(sessionId). That is one way of doing it, probably easiest way. Commented Jan 18, 2021 at 7:14
  • when do you want to set new_password_required to true? Commented Jan 18, 2021 at 9:05
  • @Amaarrockz I figured it out, see answer below. I wanted to set new_password_required to true if the parent's loginUser() fetch response had X-Session-Id in the headers. This header gets returned by my backend golang lambda if the backend's request to aws cognito InitiateAuth() method responds telling me to reset the password for the user (which requires a session-id). Commented Jan 18, 2021 at 9:38

2 Answers 2

2

Your child component looks good, however, you need to pass the props through in the parent component as shown here:

<login-form @login:user="loginUser($event)" :session-id="xidhere"
    :new-password-required="newPasswordRequired"/>

As these values are updated in the parent component, the child component should be updated.

As a note, name your props using camel case, and then use kebab-case in your HTML.

So your login-form props should be updated to:

  props: {
    sessionId: String,
    newPasswordRequired: {
      type: Boolean,
      default: () => false
    }
  },

Also, as you are emitting the event to parent, there may be no need to send the session id to the child, just add this to your api call before you send it.

Sign up to request clarification or add additional context in comments.

2 Comments

Cheers. Haven't quite nailed the CamelCase here kebab-case there bits. I know it's a thing but it's been de-prioritized ha. This is a better solution than what I've come up with as well as there's only one component and therefore less duplication. Dunno how making a separate component magically caused me to figure out the that I need to send the prop to the child with :new-password
Good work. Little by little you will get there! Keep it up!
0

Figured it out. I created a new child component for resetting password. Perhaps it can be dry'd up a bit? I'm new at this. Happy for any pointers :)

PARENT

<template>
  <div id="app" class="small-container">
    <login-form v-if="!new_password_required" @login:user="loginUser($event)" />
    <reset-password-form
      v-if="new_password_required"
      :session_id="session_id"
      @reset:password="resetPassword($event)"
    />
  </div>
</template>

<script>
import LoginForm from "@/components/LoginForm.vue";
import ResetPasswordForm from "@/components/ResetPasswordForm.vue";

export default {
  name: "Login",
  components: {
    LoginForm,
    ResetPasswordForm
  },
  data() {
    return {
      session_id: "",
      new_password_required: false
    };
  },
  methods: {
    async loginUser(loginEvent) {
      try {
        const response = await fetch(
          process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/login/user",
          {
            method: "POST",
            body: JSON.stringify(loginEvent)
          }
        );
        const data = await response.json();
        console.log(data);
        if (data.headers["X-Session-Id"] != null) {
          this.session_id = data.headers["X-Session-Id"];
          this.new_password_required = true;
        }
      } catch (error) {
        console.error(error);
      }
    },

    async resetPassword(resetPasswordEvent) {
      try {
        const response = await fetch(
          process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/reset/user/password",
          {
            method: "POST",
            body: JSON.stringify(resetPasswordEvent)
          }
        );
        const data = await response.json();
        console.log(data);
      } catch (error) {
        console.error(error);
      }
    }
  }
};
</script>

CHILD: login-form

<template>
  <div id="login-form">
    <h1>Serverless Release Dashboard</h1>
    <form>
      <label for="email_address">Email Address:</label><br />
      <input
        v-model="login_details.email_address"
        type="text"
        id="email_address"
        name="email_address"
      /><br />
      <label for="password">Password:</label><br />
      <input
        v-model="login_details.password"
        type="password"
        id="password"
        name="password"
      />
    </form>
    <button @click="loginUser($event)">
      Login
    </button>
  </div>
</template>

<script>
export default {
  name: "LoginForm",
  data() {
    return {
      login_details: {
        email_address: "",
        password: ""
      }
    };
  },

  methods: {
    loginUser() {
      console.log("testing loginUser...");
      const loginEvent = {
        email_address: this.login_details.email_address,
        password: this.login_details.password
      };
      this.$emit("login:user", loginEvent);
    }
  }
};
</script>

CHILD: reset-password-form

<template>
  <div id="reset-password-form">
    <h1>Serverless Release Dashboard</h1>
    <form>
      <label for="email_address">Email Address:</label><br />
      <input
        v-model="login_details.email_address"
        type="text"
        id="email_address"
        name="email_address"
      /><br />
      <label for="password">Password:</label><br />
      <input
        v-model="login_details.password"
        type="password"
        id="password"
        name="password"
      />
      <label for="new_password">New Password:</label><br />
      <input
        v-model="login_details.new_password"
        type="password"
        id="new_password"
        name="new_password"
      />
    </form>
    <button @click="resetPassword($event)">
      Reset Password
    </button>
  </div>
</template>

<script>
export default {
  name: "ResetPasswordForm",

  props: {
    email_address: String,
    password: String,
    session_id: String
  },

  data() {
    return {
      login_details: {
        email_address: "",
        password: "",
        new_password: "",
        session_id: ""
      }
    };
  },

  methods: {
    resetPassword() {
      console.log("testing resetPassword...");

      const loginEvent = {
        email_address: this.email_address,
        password: this.password,
        new_password: this.login_details.new_password,
        session_id: this.session_id
      };
      this.$emit("reset:password", loginEvent);
    }
  }
};
</script>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.