UNPKG

3.43 kBJavaScriptView Raw
1const _ = require('lodash')
2const jwt = require('jsonwebtoken')
3
4/**
5 * @function
6 * @private
7 *
8 * Extract bearer token out of header.
9 * https://tools.ietf.org/html/rfc7519
10 *
11 * @param {string} field The header field to be scanned
12 * @returns {string} The extracted header
13 */
14function getToken (field) {
15 return /^(?:bearer) ([a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?)$/i.exec(field)
16}
17
18/**
19 * @function
20 * @private
21 *
22 * Get function prefixing role with respective key.
23 *
24 * @param {string} clientId The current client its identifier
25 * @param {string} key The role its key
26 * @returns {Function} The composed prefixing function
27 */
28function prefixRole (clientId, key) {
29 return (role) => clientId === key ? role : `${key}:${role}`
30}
31
32/**
33 * @function
34 * @private
35 *
36 * Get roles out of token content.
37 * Exclude `account` roles and prefix realm roles
38 * with `realm:`. Roles of other resources are prefixed
39 * with their name.
40 *
41 * @param {string} clientId The current client its identifier
42 * @param {Object} [realm={ roles: [] }] The realm access data
43 * @param {Object} [resources={}] The resource access data
44 * @param {Object} [auth={ permissions: [] }] The fine-grained access data
45 * @returns {Array.<?string>} The list of roles
46 */
47function getRoles (clientId, {
48 realm_access: realm = { roles: [] },
49 resource_access: resources = {},
50 authorization: auth = { permissions: [] }
51}) {
52 const prefix = prefixRole.bind(undefined, clientId)
53 const realmRoles = realm.roles.map(prefix('realm'))
54 const scopes = _.flatten(_.map(auth.permissions, 'scopes')).map(prefix('scope'))
55 const appRoles = Object.keys(resources).map((key) => resources[key].roles.map(prefix(key)))
56
57 return _.flattenDepth([realmRoles, scopes, appRoles], 2)
58}
59
60/**
61 * @function
62 * @private
63 *
64 * Get expiration out of token content.
65 * If `exp` is undefined just use 60 seconds as default.
66 *
67 * @param {number} exp The `expiration` timestamp in seconds
68 * @returns {number} The expiration delta in milliseconds
69 */
70function getExpiration ({ exp }) {
71 return exp ? (exp * 1000) - Date.now() : 60 * 1000
72}
73
74/**
75 * @function
76 * @private
77 *
78 * Get necessary user information out of token content.
79 *
80 * @param {Object} content The token its content
81 * @param {Array.<?string>} [fields=[]] The necessary fields
82 * @returns {Object} The collection of requested user info
83 */
84function getUserInfo (content, fields = []) {
85 return _.pick(content, ['sub', ...fields])
86}
87
88/**
89 * @function
90 * @public
91 *
92 * Get various data out of token content.
93 * Get the current scope of the user and
94 * when the token expires.
95 *
96 * @param {string} tkn The token to be checked
97 * @param {string} clientId The current client its identifier
98 * @param {Array.<?string>} [userInfo] The necessary user info fields
99 * @returns {Object} The extracted data
100 */
101function getData (tkn, { clientId, userInfo }) {
102 const content = jwt.decode(tkn)
103 const scope = getRoles(clientId, content)
104
105 return {
106 expiresIn: getExpiration(content),
107 credentials: Object.assign({ scope }, getUserInfo(content, userInfo))
108 }
109}
110
111/**
112 * @function
113 * @public
114 *
115 * Get token out of header field.
116 *
117 * @param {string} field The header field to be scanned
118 * @returns {string|false} The token or `false` dependent on field
119 */
120function create (field) {
121 const match = getToken(field)
122
123 return match ? match[1] : false
124}
125
126module.exports = {
127 create,
128 getData
129}