jscas-server
Version:
An implementation of Apereo's CAS protocol
242 lines (196 loc) • 10.2 kB
Markdown
# Plugins API
*JSCAS* is designed to be modular. The server itself implements the most
basic functionality of the [CAS protocol][casp] and relies on a set of plugins
to handle the rest. Plugins fit within one of a few categories:
+ [`auth`](#auth-plugins): provides an object that can be used to validate
credentials (username and password)
+ [`attributesResolver`](#attributes-resolver-plugins): provides an object that
can be used to retrieve extended attributes for a given user
+ [`misc`](#misc-plugins): provides various add-on functionality that isn't
necessarily specific to the CAS protocol
+ [`serviceRegistry`](#service-registry-plugins): provides functionality to
create, manage, and validate allowed services
+ [`theme`](#theme-plugins): provides templates for various parts of the user
interactive portions of the protocol (e.g. a login form)
+ [`ticketRegistry`](#ticket-registry-plugins): provides functionality to
create and validate tickets
Each *JSCAS* instance requires:
+ At *most* one `theme` plugin
+ At *most* one `ticketRegistry` plugin
+ At *most* one `serviceRegistry` plugin
+ At *most* one `attributesResolver` plugin
+ At least one `auth` plugin
*JSCAS* plugins are actually [Fastify plugins][fastifyplugins] with an
additional requirement that the exported function include a `pluginName`
property. As an example, we might have the following `misc` type plugin:
```js
const fp = require('fastify-plugin')
function fooPlugin (server, options, next) {
server.get('/some/new/endpoint', function (req, reply) {
reply.send({success: true})
})
next()
}
module.exports = fp(fooPlugin)
module.exports.pluginName = 'foo'
```
This allows *JSCAS* plugins a vast amount of flexibility. We look forward
to seeing what you are able to do with this API. Read the following sections
for details on how to implement plugins of specific types within this
framework.
[casp]: https://github.com/apereo/cas/blob/1f3be83298/docs/cas-server-documentation/protocol/CAS-Protocol-Specification.md
[fastifyplugins]: https://www.fastify.io/docs/latest/Plugins/
<a id="auth-plugins"></a>
## `auth` Plugins
Authentication plugins require the registration of an object that contains
an asynchronous `validate` method; this object is called an "authenticator."
When a user attempts to authenticate, *JSCAS* will iterate all registered
authenticators until either one succeeds or all fail. If the credentials
supplied to the authenticator are valid, the authenticator must return `true`.
Otherwise it should return `false`. If an error occurs, it should be thrown.
Example:
```js
const fp = require('fastify-plugin')
module.exports = fp(function (server, options, next) {
const authenticator = {
validate: async function (username, password) {
return username === 'foo' && password === 'bar'
}
}
server.registerAuthenticator(authenticator)
next()
})
module.exports.pluginName = 'foo-authenticator'
```
Note: thrown errors and `false` results are ignored. An `error` level log will
be registered for each failed authenticator, but no other action is taken.
Reference implementation: [/lib/plugins/jsIdP](/lib/plugins/jsIdP/index.js)
<a id="attributes-resolver-plugins"></a>
## `attributesResolver` Plugins
During service ticket validation it is possible for extended user attributes
to be returned along with a successful ticket validation. In order for these
attributes to be added to the response there must be an `attributesResolver`
plugin registered with the *JSCAS* server. Such a plugin is merely an object
with a single method: `async function attributesFor (username) {}`.
The `attributesFor` method must return an empty object, `{}`, if no attributes
for the specified user could be found. Otherwise, it should return an object
with keys that represent the attribute names and values the attribute values.
If a user is a member of a set of groups, it is recommended that the attribute
name be `memberOf`.
To register an attributes resolver plugin invoke the
`server.registerAttributesResolver(resolver)` method.
<a id="misc-plugins"></a>
## `misc` Plugins
Miscellaneous plugins are not directly used by *JSCAS*. They are merely a way
to register new functionality. These plugins have access to all of the normal
Fastify APIs, and the *JSCAS* specific plugin APIs.
To register a miscellaneous plugin invoke the
`server.registerMiscPlugin(obj)` method.
<a id="service-registry-plugins"></a>
## `serviceRegistry` Plugins
A service registry plugin is a vital part of the *JSCAS* server. Only one
service registry may be present. A service registry is an object with the
methods:
+ `async function close ()`: invoked on server shutdown to clean up any
connections established by the plugin.
+ `async function getServiceWithName (name)`: invoked by the server to retrieve
services based on their human readable name.
+ `async function getServiceWithUrl (url)`: invoked by the server to retrieve
services based on their registered URL.
A "service" is an object with properties:
+ `name` (string): a human readble name for the service.
+ `comment` (string): a human redable description for the service.
+ `url` (string): the URL the remote service will send as its callback URL.
To register a service registry invoke the
`server.registerServiceRegistry(registry)` method. For example:
```js
const fp = require('fastify-plugin')
module.exports = fp(function (server, options, next) {
server.registerServiceRegistry({
close: async function () {},
getServiceWithName: async function (name) {
return {name, comment: 'example', url: 'http://example.com/casauth'}
},
getServiceWithUrl: async function (url) {
return {name: 'example', comment: 'example', url}
}
})
next()
})
module.exports.pluginName = 'foo-service-registry'
```
Reference implementation: [/lib/plugins/jsServiceRegistry](/lib/plugins/jsServiceRegistry/index.js)
<a id="theme-plugins"></a>
## `theme` Plugins
*JSCAS* supports only one theme per instance, and a theme is required. The theme
is what provides templated HTML to users when they are interacting with the
server for authentication. A theme is an object with the following methods
that return an object compatible with Fastify's [send method][fastify-send]
(e.g. a string of HTML):
+ `function login (context)`: renders the login page. The `context` is an object
with at least a `csrfToken` property. It will typically also contain a `service`
property that represents the destination service URL. It may also contain an
`error` property that will be an instance of `Error` indicating something
went wrong with the previous login attempt.
+ `function logout ()`: shown to the user when they request the `/logout` endpoint.
+ `function noService ()`: shown to the user after a successful login when they
did not supply a destination service URL.
+ `function serverError (context)`: may be shown to the user upon a 5xx error.
The `context` is an object that may contain an `error` property that will be
an instance of `Error` indicating what went wrong.
+ `function unauthorized ()`: may be shown to the user when their ticket granting
ticket, or session, cannot be validated.
+ `function unknownService (serviceUrl)`: shown to the user if the service they
are attempting to authenticate to is not registered with the server. The
`serviceUrl` parameter will be a string representation of the requested
service URL.
To register a theme plugin invoke the `server.registerTheme(theme)` method.
Reference implementation: [/lib/plugins/basicTheme](/lib/plugins/basicTheme/index.js)
[fastify-send]: https://www.fastify.io/docs/latest/Reply/#send
<a id="ticket-registry-plugins"></a>
## `ticketRegistry` Plugins
A ticket registry plugin is a vital part of the *JSCAS* server. Only one
ticket registry may be present. A ticket registry is an object with the
methods:
+ `async function genST (tgtId, serviceId, expires)`: used to generate a new
service ticket for the `serviceId` (likely the service URL) that is tied to a
ticket granting ticket with id `tgtId`. The ticket will expire at the date and
time specified by `expires` (an instance of `Date`). The method should return
a new instance of a [service ticket](Tickets.md#serviceTicket).
+ `async function genTGT (userId, expires)`: used to generate a new [ticket
granting ticket](Tickets.md#ticketGrantingTicket) for the user with `userId`
(typically their username). The ticket will expire at the date and time
specified by `expires` (an instance of `Date`).
+ `async function getST (stId)`: retrieve a previously created service ticket
that has the given ticket id `stId`.
+ `async function getTGT (tgtId)`: retrieve a previously create ticket granting
ticket that has the given ticket id `tgtId`.
+ `async function getTGTbyST (stId)`: retrieve the ticket granting ticket that
was used to generate the service ticket identified by `stId`.
+ `async function invalidateST (stId)`: used to set the `valid` property of a
service ticket to `false`. It should return the newly invalidated service ticket.
+ `async function invalidateTGT (tgtId)`: used to set the `valid` property of a
ticket granting ticket to `false`. It may return the newly invalidated
ticket granting ticket.
+ `async function servicesLogForTGT (tgtId)`: used to get the list of services
that requested authorization against the ticket granting ticket identified by
`tgtId`. It should always return an `Array`.
+ `async function trackServiceLogin (st, tgt, serviceUrl)`: used to track a
service authorization using the given service ticket `st`, ticket granting
ticket `tgt` and service URL `serviceUrl`.
To register a ticket registry invoke the
`server.registerTicketRegistry(registry)` method. For example:
```js
const fp = require('fastify-plugin')
module.exports = fp(function (server, options, next) {
server.registerTicketRegistry({
genST: async function (tgtId, expires, serviceId) {},
// ...
// ...
trackServiceLogin: async function (st, tgt, serviceUrl) {}
})
next()
})
module.exports.pluginName = 'foo-ticket-registry'
```
Reference implementation: [/lib/plugins/jsTicketRegistry](/lib/plugins/jsTicketRegistry/index.js)