A simple user authentication and management system for Google Cloud HTTP Functions.
It stores user data in Google Datastore using gstore-node; hashes passwords with bcrypt; and manages browser sessions with client-session.
Create a new Google HTTP Function with the following code:
const users = require('google-function-authorizer')({
session: {secret: 'MYSECRETKEY'} // required!
});
exports.handleRequest = function (req, res) {
users.handle(req, res);
};
Add the library to package.json:
{
"name": "your-function",
"version": "0.0.1",
"dependencies": {
"google-function-authorizer": "0.1.0"
}
}
Finally, make sure the entry point is correct. In the example above, it should be handleRequest
.
Then, assuming you named your function "users", the following endpoints will be served by your function:
POST /users
POST /users/signin
POST /users/signout
GET /users
GET /users/:id
PUT|PATCH /users/:id
DELETE /users/:id
DELETE /users/signin
OPTIONS /users(/*)
Read more about how each endpoint works in the next section.
All actions respond with a JSON object with a code
attribute with the following possible string values:
OK
- action has completed successfullyBAD_REQUEST
- action has failed due to request not being in expected formatNOT_FOUND
- endpoint not found, or user not foundUNAUTHORIZED
- action requires that a user signs in firstFORBIDDEN
- action requires that signed-in user has permission to perform itINTERNAL_ERROR
- an unexpected error has occurred while performing the action
POST /users
This endpoint creates a new user.
Request Body | Response |
---|---|
{
"username": "MyUsername",
"email": "myemail@test.com",
"password": "abc123"
} |
{
"code": "OK",
"user": {
"id": "12345",
"username": "MyUsername",
"email": "myemail@test.com"
}
} |
POST /users/signin
Signs a user in, starting a new session.
This endpoint sets a session cookie upon successful response, which will be sent in the next requests.
Request Body | Response |
---|---|
{
"email": "myemail@test.com",
"password": "abc123"
} |
{
"code": "OK",
"user": {
"id": "12345",
"username": "MyUsername",
"email": "myemail@test.com"
}
} |
POST /users/signout
DELETE /users/signin
(alternatively)
Signs a user out, removing the session cookie.
Request Body | Response |
---|---|
(empty) |
{
"code": "OK"
} |
GET /users
Returns a list of users with pagination. Default page size is 20.
By default, this endpoint requires the signed-in user role to be admin
.
Request Query Parameters | Response |
---|---|
|
{
"code": "OK",
"items": [
{"id": "1", "username": "user1", "email": "email1@test.com", ...},
{"id": "2", "username": "user2", "email": "email2@test.com", ...},
...
],
"limit": 20,
"next": "NextPageKey000123"
} |
The next
attribute will be absent from the response if there are no more entries to fetch.
(Filters and sorting are not yet supported.)
GET /users/:id
Returns data of a single user.
By default, this endpoint has the following requirements:
- user ID being requested matches with signed-in user ID; or
- the signed-in user role is
admin
.
Request URI | Response |
---|---|
|
{
"code": "OK",
"user": {
"id": "12345",
"username": "MyUsername",
"email": "myemail@test.com"
}
} |
PUT /users/:id
PATCH /users/:id
Updates data of a single user, including password.
Both PUT
and PATCH
methods behave the same way, and partial data can be provided.
By default, this endpoint has the following requirements:
- user ID being updated matches with signed-in user ID; or
- the signed-in user role is
admin
.
(Not yet implemented) The user role
attribute can only be updated by other admins.
Request Body | Response |
---|---|
{
"username": "EditedUsername"
} |
{
"code": "OK",
"user": {
"id": "12345",
"username": "EditedUsername",
"email": "myemail@test.com"
}
} |
DELETE /users/:id
Removes a user.
By default, this endpoint has the following requirements:
- user ID being deleted matches with signed-in user ID; or
- the signed-in user role is
admin
.
If user being deleted matches the user currently signed in, the user will be automatically signed out.
Request Body | Response |
---|---|
(empty) |
{
"code": "OK"
} |
OPTIONS /users
Some clients will fire a "preflight request" prior to making the real request to verify CORS options, which are supported by this library.
You can also use this method to retrieve the current signed-in user, which will be present in the response body, as well as the current endpoint rules (see Configuration for details), which the client can then use to prevent certain actions on their side.
Request Body | Response |
---|---|
(empty) |
{
"code": "OK",
"userId": "12345",
"rules": {
"signin": "all",
"create": "all",
"update": "self",
"find": "self",
"list": "admin",
"destroy": "self"
}
} |
Call authorize
in your other Google Functions to have the session cookie read from the request before calling your code:
const users = require('google-function-authorizer')({
session: {secret: 'MYSECRETKEY'} // required! must be the same as your users endpoint!
});
exports.handleRequest = function (req, res) {
users.authorize(req, res, mainFunction);
};
function mainFunction (req, res, user) {
// session is valid -- execute the rest of your function
}
If the session is valid, a user
object will be passed along to your main function, containing a subset of stored attributes.
If the session is invalid, a 401 - Unauthorized
error will be returned automatically. If you want to take control of error processing, pass a fourth argument to authorize
with an error function that takes req
and res
arguments.
This library comes with a very simple role management system built-in.
The default rules are:
- Anyone can create a user or sign in.
- Users with
admin
role are able to edit and delete other users. - Users with any other role are only allowed to edit or delete themselves.
- Only admins can list users.
- Users cannot be created with a role, since that specific endpoint is public.
- Another admin must edit newly created users if they need defined roles.
These rules are customizable. Refer to the next section to learn how to change them.
Settings can be customized upon requiring the library, and have the following defaults:
const users = require('google-function-authorizer')({
// Configure session cookie behavior here.
session: {
name: 'userSession', // cookie name
secret: null, // must be set! will raise error (and crash) if not defined
duration: 24 * 60 * 60 * 1000, // session expiration in ms
activeDuration: 1000 * 60 * 5 // session active duration in ms
},
// Datastore settings.
datastore: {
kind: 'User',
namespace: undefined
},
// You can overwrite the full schema, but it needs at least:
// - the primary field, and
// - the password field.
// Both field names can be defined in the "fields" section.
// If your schema is missing "confirmation" fields,
// confirmation workflows will not run.
schema: {
username: {
type: 'string',
optional: true,
excludeFromIndexes: true
},
email: {
type: 'string',
required: true,
validate: 'isEmail'
},
password: {
type: 'string',
required: true,
read: false,
excludeFromIndexes: true
},
role: {
type: 'string',
write: false
},
confirmationToken: {
type: 'string',
read: false
},
confirmedOn: {
type: 'datetime',
excludeFromIndexes: true
},
createdOn: {
type: 'datetime',
write: false,
default: Gstore.defaultValues.NOW,
excludeFromIndexes: true
},
modifiedOn: {
type: 'datetime',
write: false,
excludeFromIndexes: true
}
},
// If you use a custom schema, you can have custom field names as well.
fields: {
primary: 'email', // will be used in signin with password
username: 'username',
email: 'email',
password: 'password',
role: 'role'
},
// Customize CORS headers here to anything you'd like.
// Multiple headers are accepted.
cors: {
"Access-Control-Allow-Origin": "*"
},
// Apply rules for certain user-related actions.
// The following values are accepted:
// - "all": public and unrestricted access
// - "user": access for logged-in users only
// - "self": access for record owner only, or admins
// - "admin": admin access only
// - false: endpoint is disabled
// (useful if you want to disable user creation, for example)
// Absence of field or value will default to "admin".
rules: {
signin: 'all',
create: 'all',
update: 'self',
find: 'self',
list: 'admin',
destroy: 'self'
}
});
exports.handleRequest = function (req, res) {
users.handle(req, res);
};
Please note that settings must be the same across all functions. So if you change any of the default values, make sure to replicate them accordingly.
When Google Cloud Functions support environment variables, we will change this approach so configuration can be unified and the copy/pasting avoided.
If you want to customize Schema validators with your own functions, take a look at the Gstore Schema documentation.
- Google reCAPTCHA support on user creation and update.
- Email service support (Mailgun, SendGrid, etc) for sending various confirmation messages.
- Customizable cache TTL support for
authorization
.- Currently, it stores values in session until user signs out or session expires.
- Support other data stores (like MySQL).
- Support JSON Web Tokens instead of client-sessions.
MIT