/**
* @module flitter-auth/model/KeyAction
*/
const Model = require('flitter-orm/src/model/Model')
const uuid = require('uuid/v4')
const { ObjectId } = require('mongodb')
/**
* Represents a single available key action. Key actions
* are one-time use links that directly call a method on
* a controller. These actions:
*
* - Can pass along context
* - Have expiration dates
* - Are single-use only
* - Can automatically log in a user during the request lifecycle
* @extends module:flitter-orm/src/model/Model~Model
*/
class KeyAction extends Model {
/**
* Defines the services required by this model.
* @returns {Array<string>}
*/
static get services() {
return [...super.services, 'models', 'configs']
}
/**
* Gets the schema for this model. Provides the following fields:
* - key: String [uuid]
* - secret: String [uuid]
* - user_id: ObjectId
* - handler: String
* - created: Date [now]
* - expires: Date [now +1 day]
* - used: Boolean
* - auto_login: Boolean
* - no_auto_logout: Boolean
* - did_auto_login: Boolean
* - data: String ['{}']
* @type {object}
*/
static get schema() {
return {
key: { type: String, default: uuid },
secret: { type: String, default: uuid },
user_id: ObjectId,
handler: String,
created: { type: Date, default: () => new Date },
expires: {
type: Date,
default: () => {
const date = new Date
date.setDate(date.getDate() + 1)
return date
}
},
used: Boolean,
auto_login: Boolean,
no_auto_logout: Boolean,
did_auto_login: Boolean,
data: { type: String, default: '{}' },
}
}
/**
* Generate a filter object for this model that restricts
* the result set to unused, unexpired key actions.
* @returns {Promise<module:flitter-orm/src/filter/Filter~Filter>}
*/
static async availableFilter() {
const filter = await this.filter()
filter.field('expires').greater_than(new Date).end()
filter.field('used').not().equal(true).end().end()
return filter
}
/**
* Lookup a single key action based on the passed in filter parameters.
* Automatically restricts the result set to unused, unexpired key actions.
* @param {object} params
* @returns {Promise<module:flitter-auth/model/KeyAction~KeyAction>}
*/
static async lookup(params) {
const filter = await this.availableFilter()
filter.absorb(params)
return filter.end().findOne()
}
/**
* Get a value from the action's metadata.
* @param {string} key
* @returns {*}
*/
data_get(key) {
return JSON.parse(this.data ? this.data : '{}')[key]
}
/**
* Set a value in the action's metadata.
* @param {string} key
* @param {*} value
*/
data_set(key, value) {
const data = JSON.parse(this.data ? this.data : '{}')
data[key] = value
this.data = JSON.stringify(data)
}
/**
* Get the URL path for this key action. Uses the 'app.url' config.
* @returns {string}
*/
url() {
const config_app_url = this.configs.get('app.url')
const base_url = config_app_url.endsWith('/') ? config_app_url : `${config_app_url}/`
return `${base_url}auth/action/${this.key}`
}
/**
* Fetch the associated user for this action.
* @returns {Promise<module:flitter-auth/model/User~BaseUser|void>}
*/
async user() {
const User = this.models.get('auth:User')
return this.has_one(User, 'user_id', '_id')
}
/**
* Close out the key action. If did_auto_login is set and no_auto_logout is not set,
* log out the user and remove the key action from the session for the provided request.
* @param {express/request} request - the request
* @returns {Promise<void>}
*/
async close(request) {
if ( request.key_action && request.key_action.did_auto_login && !request.key_action.no_auto_logout ) {
const action = request.key_action
delete request.key_action
delete request.session.key_action_key
if ( action.user_id && action.auto_login && request.is_auth ) {
const provider = await request.security.provider()
await provider.logout(request)
await request.session.save()
}
}
}
}
module.exports = exports = KeyAction