Day 12 [D&D Character Sheets]
Tags: 100daysofcode
Published: 2020-03-25 08:31:11

#  Report

Today I got user authentication working with passport. I started setting by setting up my password hashing. I noticed that in the passport documentation that they were using methods on the user object to compare and hash passwords and had an realization. I never made the connection to do store the hashing logic on the model but I loved the pattern. Password hashing is intrinsically tied to the user object and it makes sense to store them together, it also make maintenance easier. With this in mind, I made a static and instance method on my player schema to handle password hashing.

```js
// db/models/player/index.js
const Player= new Schema({...});

Player.methods.checkPassword = function(plainTextPassword) {
	return bcrypt.compare(plainTextPassword, this.password)
}

Player.statics.hashPassword = function(plainTextPassword) {
	return bcrypt.hash(plainTextPassword, 10)
}

module.exports = Player
```

You can see the static method being used in the seeder. And the instance method in the passport local strategy.

```js
// db/seeder.js
const player = await db.models.Player.create({
    username: "admin",
    email: "ephriamhenderson@ephriamhenderson.dev",
    // Static method that hashes user password.
    password: await db.models.Player.hashPassword("password") 
});
```

```js
// helpers/init-passport.js
const player = await db.models.Player.findOne({
    email: email.toLowerCase()
});
// ...
// The instance method that compares the password.
let isMatch = await player.checkPassword(password);
```

Getting passport working properly took a bit of debugging. First the local strategy didn't seem to be running. Soon I realized that I needed to define the name of my username and password field. 

```js
// helpers/init-passport.js
// broken
passport.use(
    new LocalStrategy(
        async (email, password, done) => {
            logger.debug(`Auth attempt for: ${email}`);
            const db = await dbPromise;
            const player = await db.models.Player.findOne({
                email: email.toLowerCase()
            });

            if (!player) {
                logger.debug(`No email found for: ${email}`);
                return done(null, false, { message: "Incorrect Credentials" });
            }

            let isMatch = await player.checkPassword(password);

            if (!isMatch) {
                logger.debug(`Incorrect password for: ${email}`);
                return done(null, false, { message: "Incorrect Credentials" });
            }
            logger.debug(`${email} logged in successfully.`);

            return done(null, player);
        }
    )
);
```

```js
// helpers/init-passport.js
// fixed
passport.use(
		new LocalStrategy(
			{
                // define the name of the username and password fields
				usernameField: "email",
				passwordField: "password"
			},
			async (email, password, done) => {
				logger.debug(`Auth attempt for: ${email}`);
				// Make sure the db is connected and and the db object returned
                const db = await dbPromise;
				const player = await db.models.Player.findOne({
					email: email.toLowerCase()
				});
				
                // Does player with that email exist? If not send an error.
				if (!player) {
					logger.debug(`No email found for: ${email}`);
					return done(null, false, { message: "Incorrect Credentials" });
				}

				let isMatch = await player.checkPassword(password);
				
                // Does the password match? If not send and error.
				if (!isMatch) {
					logger.debug(`Incorrect password for: ${email}`);
					return done(null, false, { message: "Incorrect Credentials" });
				}
				logger.debug(`${email} logged in successfully.`);

				return done(null, player);
			}
		)
	);
```

Once I got the local strategy to run correctly I encountered an issue with my login post route. 

```js
// router/users/index.js
router.post(
	"/login",
	bodyParser.urlencoded({ extended: true }),
    // This should redirect depending on failure or success.
	passport.authenticate("local", {
		successRedirect: "/",
		failureRedirect: "/users/login"
	})
);
```

Passport wouldn't redirect correctly, when I made a request it would hang without throwing an error. Almost as if I had forgot to send a response in a normal route. Eventually I found the problem in my passport serialization.

```js
// helpers/init-passport.js
// broken
passport.serializeUser((user, done) => {
    logger.debug(`Serializing ${user.email}`);
    done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
    logger.debug(`Deserializing user: ${id}`);
    const db = await dbPromise;
    let user = db.models.Player.findById(id);
});
```

```js
// helpers/init-passport.js
// fixed
passport.serializeUser((user, done) => {
    logger.debug(`Serializing ${user.email}`);
    done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
    logger.debug(`Deserializing user: ${id}`);
    const db = await dbPromise;
    let user = db.models.Player.findById(id);
    // I had forgot to call done.
    done(null, user);
});
```

 I'm happy with today. I had a good time debugging this issue and I got authentication working fairly easily with passport. As a junior dev I still have a ton to learn about security and would be interested in learning how to make my passwords more secure, am I making any blatant security mistakes? Any tips for best practices? Feel free to tell me how bad I am at this!

# Project

{% github ApproachingApathy/100days-dnd-character-sheets %}

The First project will be an app to keep D&D character sheets.

## Stack

I'll be using Node.js and building a full-stack Express app with MongoDB.
### Requirements
#### Minimum Viable 

- [ ] Investigate automating or finding a source of info for the data in the SRD.
- [ ] Present a D&D Character Sheet
    - [ ] The sheet should display all the same info as the first page of the 5e Official sheet.
- [ ] Users should be able to log in and create player-characters.
- [ ] Users should be able to edit character sheets.
- [ ] Users should be able to organize character sheets into groups (parties/tables)
- [ ] Sheets should auto calculate basic stats like ability modifiers.
    - [ ] Support Proficiency Bonuses 

### Cake
- [ ] Extend character creation to allow the user to use any of the three common stat gen methods.
    - [ ] Point Buy
    - [ ] Standard Array
    - [ ] Roll
- [ ] Extend the character sheet to all the info in the 5e official sheet.
- [ ] Allow for image uploads for character portraits.
- [ ] Allow for extended descriptions/backstories.
    - [ ] Characters should have nice full page backstories.
    - [ ] Preferably use a markdown editor.