# Teams

The teams feature is where team management code is located. This allows team owners to add/remove/modify team members, and restrict access based on permissions and roles.

# Enabling

The teams feature is enabled by default, but if your service does not require team management, you can disable this feature by modifying the features.wood key in app/config/app.js and omitting the teams feature:

  features: {
    app: [
      // your app features here
    ],

    wood: [
      'users',
      'admin',
      'subscriptions',
      // 'teams',
    ],
  },

It is important to note that all users will have a team, regardless of if the teams feature is enabled or not, and things like subscriptions will be attached to the team, not the user. This is a best practice to ensure that if you one day need to enable teams for your app, you are saved a truly monumental amount of conversion/migration work. In the meantime, it does not add a significant amount of overhead.

# Configuring

Team configuration values are stored in config/teams.js.

# permissions and roles:

This is where you define roles that can be assigned to users, and permissions those roles grant, like so:

const permissions = {
  manage_team: {
    name: 'Manage Team',
    description: 'Invite/manage/remove team members.',
  },
  order_supplies: {
    name: 'Order Supplies',
    description: 'Order supplies to be used for training.',
  },
  // ...
};

const roles = {
  owner: {
    name: 'Coach',
    description: 'Manages team, can order supplies.',
    permissions: ['manage_team', 'order_supplies'],
  },
  // ...
};

The ID of a permission is its key in the permissions object (manage_team, order_supplies in the example). The name and description are used when assigning a new role to a team member, so you can be sure of what permissions are being granted to them when granting them the new role.

The role object works similarly. The ID is the key in the roles object, and this is what is stored in the database and connected with the user. The name and description are self-descriptive, and the permissions attribute is an inclusive list of the IDs of the permissions this role grants.

# teamNameOptional:

When true, the team name on the signup form is optional. If not provided, the team name will be set to ${userName}'s Team.

# subscriptionMemberLimits:

If this field is set to true, the meta.max_members field in each product in config/stripe/products.json will configure the maximum number of team members allowed on a team, when that team is subscribed to that product. Any number value in max_members will enforce that as the maximum number of members allowed on that team, while if that number is null, undefined, or simply missing, it means there is no limit placed on that product.

# inviteExpiry:

This is the amount of time after an invite is issued that it remains valid for. These values are used as parameters for Moment.js's manipulation functions (opens new window).

# language:

You may not want to refer to your teams as "Teams", instead opting to use something more-specific, like "Companies", or less-specific, like "Organizations". The language object contains all all strings in the Teams feature that refer to teams as "Teams". By changing the text in these fields, you can change what "Teams" appear as to your users in the UI and in API responses.

# Important URLs

The primary important URL is /team, which leads to the team management page. It can be found in the user drop-down in the top right, under "Team". On this page, a list of all members on the team will be displayed, as well as an "Action" column for changing the team member roles, removing team members, etc. This column, and all team management functions, will only be displayed to or usable by team members with a role that grants the manage_team permission.

# Database tables

There are two database tables in place to provide team functionality:

# teams

This table contains the team name (defaults to the primary user name when the teams feature is disabled) as well as Stripe subscription information.

# users_teams

This table links teams to users by way of a composite primary key (team_id, user_id) and a role field that is the ID of the role this user has been given. By default, the first user on a team will be granted the owner role.

# API: Team and Role in the Request object

In addition to the user object attached to the req object of every controller route, there will also be a team and role object. These are parsed out from the user's JWT, and then confirmed, to ensure that the user's role hasn't changed or that they haven't been removed from the team. (In these cases, the user's jwt_series is also updated, which should immediately log out the user, for further security.)

The team object is an instance of TeamModel and is only really useful if you want the team name or Stripe information.

The role object is an instance of RoleModel and is the primary method of checking if the user has permission to access the route in question. (See checking permissions.)

# UI: Team and Role in Vuex stores

The active user's team and role are stored in the Users store, under team and role, respectively.

The team object is an instance of TeamModel and is only really useful if you want the team name or Stripe information.

The role object is an instance of RoleModel and is the primary method of checking if the user has permission to access the route in question. (See checking permissions.)

# Checking permissions

Once you have a user's RoleModel, you can check and see if they have permissions to access a particular resource. The simplest way is to just ask the model itself:

  if (! req.role.hasPermissions(['access_secret_area'])) {
    throw new Error('NOPE');
  }

However, in API controllers, you can create an Express middleware to make it easy to enforce permissions on a per-route basis:

const requireTopSecret = this.requirePermissions(['top_secret']);

this.router.get('/secrets/:id', requireTopSecret, this.showSecrets.bind(this));

# TeamModel

The TeamModel represents a single team in your application, and has the following properties:

  • id: The internal ID of the team. This isn't ever sent directly to the application. Instead, Hashids (opens new window) are used to obfuscate this value before it is sent via the API.
  • name: The name of the team. This defaults to "UserName's Team" if the user declines to enter a anme.
  • currency: The currency code this team's subscription is charged in. Defaults to USD.
  • stripeCustomerId: If the team has a subscription, this is the Stripe customer ID for that subscription.
  • createdAt: A timestamp indicated when the team was created.
  • updatedAt: A timestamp indicating when the team was last updated.
  • flags: A JSONB object that can store any value. Should be used to store values that a team may only occasionally have set. More-permanent values should have their own columns defined. These values can be queried either from the database (opens new window) or directly off the model with the hasFlag() function.
  • secureFlags: A JSONB object that can store any value, similar to flags. However, these values are not serialized in toJSON() and thus will never be sent over the API. These values can be queried either from the database (opens new window) or directly off the model with the hasFlag() function.
  • subscription: A SubscriptionModel object, if the team has a subscription.