loading...

Day 21 [D&D Character Sheet]

approachingapathy profile image Ephriam Henderson ・3 min read

Report

Today I got stuck for a while trying to implement something I thought would be cool. The idea was to encapsulate all password hashing and comparison functions into the Player Model. I've already started down this path by attaching my hashing and password comparison functions to a static class and instance method respectively. The goal was to take that further. I would use a setter method on the password field to hash the password. Right now inserting a password looks like this:

user.password = db.models.User.hashPassword(password)

After my changes it would look like this:

user.password = password

The model would handle the hashing internally. Here's where I got stuck, after I setup the setter function I continued getting the error Player validation failed: password: Cast to String failed for value "password" at path "password". After nearly an hour of googling, I found the answer. Mongoose doesn't support asynchronous setters. That error is likely the result of mongoose trying to cast a promise into a string. I could have continued with the plan a couple of ways.

1. Switch to synchronous hashing.

I could use bcrypt in synchronous mode. The problem is the bcrypt is designed to be slow. And while I don't think my app is going to get many, if any users, blocking the stack every time a user signed-up or changed their password felt like and unacceptable compromise.

2. Use an instance method

I could use an instance method to hash and set the password. The problem with this is on the first creation of the document I could not think of a way to set the password. For Instance:

// No longer would the password be set like this.
db.models.User.create({email: email, password: password})
// It would be something like this.
db.models.User.create({email}).setPassword(password)

The problem is that .create() saves the model to the database. Why is this problem? What is the password on that first save? I could set a default, maybe an empty string, but then for however short a time period I'd have an account in my database that had an insecure(empty or some default value) password in my database.

What if something went wrong before hashPassword() could complete? I could log in to an account with an empty string! In sequelize I could use the .build() method to construct an instance before saving it, if there is an equivalent method in mongoose I don't know it yet.

Even if that method does exist, I don't like the idea of constructing a user without a password.


In the I decided that the complexities getting one of the above paths to work wasn't worth the benefits. I did end up making some changes to the schema.

// db/models/player/index.js
password: {
    type: String,
    required: true,
    /* The password is no longer retrieved by default, 
    the select key means that a developer has to specifically 
    request it. */
    select: false,
    /* This getter means that a developer that tries to access 
    the password in the conventional way will cause an error to 
    be thrown.
    This discourages retrieving the password from outside the 
    schema, something that, as constructed, a developer should 
    have little reason to do. */
    get: () => {
        let accessError = new Error(
            `Password field is not directly accessible. 
            Please use the checkPassword or hashPassword method.`
        );
        logger.error(accessError.message);
        throw accessError;
    }
},

Project

[100days] The DND Character Sheet App

This is the first project of my 100 days of coding This is 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

  • 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…

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.
  • [X] 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.

Posted on by:

approachingapathy profile

Ephriam Henderson

@approachingapathy

I'm a Full-Stack Developer and recent Bootcamp Graduate. I'm most experienced with JavaScript, but I'm a python enthusiast and an all-around nerd. When I'm not coding I love anime, sci-fi, d&d.

Discussion

pic
Editor guide