Invitation Only

  • Authors: Marc Palmer
2 votes
Dependency:
compile ":invitation-only:1.1"

 Documentation  Source  Issues

Summary

Provides a simple service and admin UI for receiving invitation requests, sending out invitation emails, and batch approval of pending invites, as well as reporting of users that have actually interacted with your application after acceptance.

Description

Want to create a buzz about your new service by limiting access, or just want to hedge your bets to see if you can scale before full public roll out?

This plugin manages user invitations to your service so that you can roll out access to your app gradually and create specific user groups limited to accessing certain functionality in your app.

All you need to do is add a check in your code to see if the user is approved for using the desired part of your app, at the point they try to do it.

A simple administration UI is provided with stats for the number of invitations issued/requested, the number that have been approved (permitted access) and the number that have actually participated in your application after being approved. You can also approved a batch of pending invites (first come first served), and manually batch-add invites that send an email to the users inviting them.

This plugin does not do any kind of security authentication or authorisation - this is completely orthogonal to your existing security mechanisms.

Commercial Support

Commercial support is available for this and other Grailsrocks plugins.

Installation

Standard drill:

grails install-plugin invitation-only

Concepts

The plugin works by tracking UserInvitation(s). These are GORM objects, although this is not important as you have a com.grailsrocks.invitationonly.InvitationService service you can inject to access all the functionality.

An invitation is bound to an email address. Invitations have an "approved" field which defaults to false.

In this way, you can create un-approved invitations with or without sending an email to the user, and these can be approved later in batches as you expand the list of users who can access your service.

Three dates are recorded for invitations - the date created, the date approved (if ever) and the date the user first participated in your application. This "first participated" date is only updated if you add code to your application to indicate that the user has interacted with the application or feature sub-set.

This is however useful if you do implement it - it enables you to see the actual take-up of your invited users once you approve them.

Making it work - adding code to your application

For the basic use case, you only need to add one line of code to your application to integrate this plugin.

Take the following scenario: you have an application in limited "beta".

In your sign-up page or similar "gateway" to using your application, you add the following code to your controller (imagine you have a doSignup action that handles the signup submission form):

// remember to inject invitationService into your controller

def doSignup = { if (!invitationService.isUserApproved(params.email, '1.0-beta')) { redirect( /* to some page saying "sorry beta users only, click here to apply for an invite" */ ) }

do your normal post signup stuff … }

That's it. The first argument to the function is the user's email address, the second is whatever name you assign to this "group" of invitations. i.e. the same user can be invited to different parts/services, and approval on each of these groups is performed individually. You could approve the same person for "1.0-beta" feature access but still keep an un-approved invitation for them to access "1.1-alpha-preview".

To use the admin interface, you browse to the AdminInvitationsController - if you have default Grails /$controller/$action? URL mappings in your app you can just browse to "<yoururl>/adminInvitations/" - if not you will have to add an explicit URL mapping for this controller.

REMEMBER TO PROTECT THIS ADMIN URL FROM UNAUTHORISED ACCESS!

In the admin interface you'll see an ugly unstyled UI that shows you the stats, a link to view and edit all invitations (essentially scaffolded) with option to individually approve users, and a form to send invitation emails with or without automatic instant approval.

You can customise the layout of the default admin page, by setting the Config variable invitation.only.admin.layout to the name of a GSP layout in your application. This way you can incorporate your app's own admin nav into the page, and style the form better.

Hopefully we can spend some time improving this UI in future. Anybody want to help?

Configuration

The following config variables are supported as of version 1.1:

  • invitation.only.admin.notify - boolean, defaults to true. Enables sending notification emails to an admin address when users request invitations, bulk invitations are performed, users are approved, and when users participate. Great for micro-monitoring your new baby!
  • invitation.only.admin.email - Email address to send admin notification mails to
  • invitation.only.server.email - Email address used as the From: address for notification mails to admin
  • invitation.only.admin.layout - Name/path to GSP layout to be used in admin area. Set this to use your own page layout and navigation.

API - InvitationService

This service is your one-stop-shop for invitation management. It is used by the admin controller.

inviteUsersToGroupAndMail

def inviteUsersToGroupAndMail(def args)

This function creates invitations for a list of email address, optionally pre-approving them, and sending them an email.

The args requres the following properties:

  • groupName - the name of the invitation group, e.g. '1.0-beta'
  • subject - the subject for the email sent to them e.g. 'Thanks for taking part in our beta!'
  • addresses - a String or list of valid email addresses. String can be comma or new-line delimited or both.
  • message - the email message body
  • senderAddress - the email address of the "sender" of the email, e.g. invites-help@yourservice.com
  • approved - boolean to indicate whether or not the users should be instantly approved for access
The return value is the list of addresses the invites were sent to.

getStatistics

def getStatistics()

This method returns a map of statistics used in the admin page, containing:

  • totalInvitationsPerGroup
  • totalApprovedInvitationsPerGroup
  • totalPendingInvitationsPerGroup
  • totalPaticipatedPerGroup
  • totalsByGroup
Each of these is a map of group name to numbers e.g. ['1.0-beta', 56]

inviteUser

def inviteUser(email, group, approved = false)

This method creates an invitation for a single user.

  • email - the email address
  • group - the group name e.g. '1.0-beta'
  • approved - (optional) if set to true, the user is instantly granted access to the group
The return value is the new UserInvitation instance created.

isUserInvited

boolean isUserInvited(email, group)

Call this to establish if a given user has an existing invitation to the specified group. This will return true if the user has any kind of invitation (approved or not).

isUserApproved

boolean isUserApproved(email, group)

Call this to establish if a given user has an existing and approved invitation to the specified group.

approve

def approve(email, group)

Call this to approve a specific user for the group. The return value is the UserInvitation instance or null if the user does not have an existing invitation for that group.

userParticipated

def userParticipated(email, group)

Call this to indicate that a specific user has "accessed" the group's functionality. You would typically call this from your post-signup controller action so that the date the user actually used your app (after the invitation being approved by you) is recorded. This is useful for reporting to see how many approved users actually use your application - so place this call carefully to make sure it is providing useful information.

The return value is the UserInvitation instance or null if the user does not have an existing invitation for that group.

approveBatch

def approveBatch(group, totalToUnlock)

Call this to approve a new batch of N invites for the given group name. For example, you can use this to approve the next 500 invites for "1.0-beta".

It will approve the first totalToUnlock non-approved invitations, in order of dateCreated, so invitations are approved "first come, first served".

Returns the number of invitations actually approved, which may be less than totalToUnlock

forEachInvitedUserInGroup

void forEachInvitedUserInGroup(group, Closure c)

Iterates over each invitation in the specified group, calling the closure with a single argument, the UserInvitation instance.

forEachApprovedInvitedUserInGroup

void forEachApprovedInvitedUserInGroup(group, Closure c)

Iterates over each approved invitation in the specified group, calling the closure with a single argument, the UserInvitation instance.

forEachPendingInvitedUserInGroup

void forEachPendingInvitedUserInGroup(group, Closure c)

Iterates over each unapproved invitation in the specified group, calling the closure with a single argument, the UserInvitation instance.