1 | const _ = require('lodash')
|
2 | const 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 | */
|
14 | function 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 | */
|
28 | function 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 | */
|
47 | function 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 | */
|
70 | function 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 | */
|
84 | function 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 | */
|
101 | function 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 | */
|
120 | function create (field) {
|
121 | const match = getToken(field)
|
122 |
|
123 | return match ? match[1] : false
|
124 | }
|
125 |
|
126 | module.exports = {
|
127 | create,
|
128 | getData
|
129 | }
|