|
1 | 1 | const _ = require('lodash')
|
2 | 2 |
|
3 | 3 | /**
|
4 |
| - * @constructor |
5 |
| - * @public |
| 4 | + * @function |
| 5 | + * @private |
6 | 6 | *
|
7 |
| - * Wrap JSON Web Token to enable additional features. |
| 7 | + * Extract bearer token out of header. |
| 8 | + * https://tools.ietf.org/html/rfc7519 |
8 | 9 | *
|
9 |
| - * @param {string} field The bearer token field |
10 |
| - * @returns {token} The token wrapper |
| 10 | + * @param {string} field The header field to be scanned |
| 11 | + * @returns {string} The extracted header |
11 | 12 | */
|
12 |
| -function token (field) { |
13 |
| - let tkn |
14 |
| - |
15 |
| - /** |
16 |
| - * @function |
17 |
| - * @private |
18 |
| - * |
19 |
| - * Extract bearer token out of header. |
20 |
| - * https://tools.ietf.org/html/rfc7519 |
21 |
| - * |
22 |
| - * @param {string} header The header to be scanned |
23 |
| - * @returns {string} The extracted header |
24 |
| - */ |
25 |
| - function extractToken (header) { |
26 |
| - return /^(?:bearer) ([a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?)$/i.exec(header) |
27 |
| - } |
28 |
| - |
29 |
| - /** |
30 |
| - * @function |
31 |
| - * @private |
32 |
| - * |
33 |
| - * Get scope out of token content. |
34 |
| - * Exclude `account` roles and prefix realm roles |
35 |
| - * with `realm:`. Roles of other resources are prefixed |
36 |
| - * with their name. |
37 |
| - * |
38 |
| - * @param {Object} [realm] The realm access data |
39 |
| - * @param {Object} [resource] The resource access data |
40 |
| - * @returns {Array.<?string>} The list of roles |
41 |
| - */ |
42 |
| - function getScope ({ |
43 |
| - realm_access: realm = { roles: [] }, |
44 |
| - resource_access: resource = {} |
45 |
| - }) { |
46 |
| - delete resource.account |
47 |
| - const realmRoles = realm.roles.map(role => `realm:${role}`) |
48 |
| - |
49 |
| - const appRoles = _.flatten(_.map(resource, 'roles')) |
| 13 | +function extractToken (field) { |
| 14 | + return /^(?:bearer) ([a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?)$/i.exec(field) |
| 15 | +} |
50 | 16 |
|
51 |
| - return [...realmRoles, ...appRoles] |
52 |
| - } |
| 17 | +/** |
| 18 | + * @function |
| 19 | + * @private |
| 20 | + * |
| 21 | + * Get scope out of token content. |
| 22 | + * Exclude `account` roles and prefix realm roles |
| 23 | + * with `realm:`. Roles of other resources are prefixed |
| 24 | + * with their name. |
| 25 | + * |
| 26 | + * @param {Object} [realm] The realm access data |
| 27 | + * @param {Object} [resource] The resource access data |
| 28 | + * @returns {Array.<?string>} The list of roles |
| 29 | + */ |
| 30 | +function getScope ({ |
| 31 | + realm_access: realm = { roles: [] }, |
| 32 | + resource_access: resource = {} |
| 33 | +}) { |
| 34 | + delete resource.account |
| 35 | + const realmRoles = realm.roles.map(role => `realm:${role}`) |
53 | 36 |
|
54 |
| - /** |
55 |
| - * @function |
56 |
| - * @private |
57 |
| - * |
58 |
| - * Get expiration out of token content. |
59 |
| - * |
60 |
| - * @param {number} [exp] The `expiration` timestamp in seconds |
61 |
| - * @param {number} [iat] The `issued at` timestamp in seconds |
62 |
| - * @returns {number} The expiration delta in milliseconds |
63 |
| - */ |
64 |
| - function getExpiration ({ exp = 60, iat = 0 }) { |
65 |
| - return (exp - iat) * 1000 |
66 |
| - } |
| 37 | + const appRoles = _.flatten(_.map(resource, 'roles')) |
67 | 38 |
|
68 |
| - /** |
69 |
| - * @function |
70 |
| - * @private |
71 |
| - * |
72 |
| - * Get necessary user information out of token content. |
73 |
| - * |
74 |
| - * @param {Object} content The token its content |
75 |
| - * @param {Array.<?string>} [fields] The necessary fields |
76 |
| - * @returns {Object} The collection of requested user info |
77 |
| - */ |
78 |
| - function getUserInfo (content, fields = []) { |
79 |
| - return _.pick(content, ['sub', ...fields]) |
80 |
| - } |
| 39 | + return [...realmRoles, ...appRoles] |
| 40 | +} |
81 | 41 |
|
82 |
| - /** |
83 |
| - * @function |
84 |
| - * @public |
85 |
| - * |
86 |
| - * Extract content out of token. |
87 |
| - * The content is the middle part. |
88 |
| - * |
89 |
| - * @returns {Object} The token its content |
90 |
| - */ |
91 |
| - function getContent () { |
92 |
| - return JSON.parse(Buffer.from(tkn.split('.')[1], 'base64').toString()) |
93 |
| - } |
| 42 | +/** |
| 43 | + * @function |
| 44 | + * @private |
| 45 | + * |
| 46 | + * Get expiration out of token content. |
| 47 | + * |
| 48 | + * @param {number} [exp] The `expiration` timestamp in seconds |
| 49 | + * @param {number} [iat] The `issued at` timestamp in seconds |
| 50 | + * @returns {number} The expiration delta in milliseconds |
| 51 | + */ |
| 52 | +function getExpiration ({ exp = 60, iat = 0 }) { |
| 53 | + return (exp - iat) * 1000 |
| 54 | +} |
94 | 55 |
|
95 |
| - /** |
96 |
| - * @function |
97 |
| - * @public |
98 |
| - * |
99 |
| - * Get various data out of token content. |
100 |
| - * Get the current scope of the user and |
101 |
| - * when the token expires. |
102 |
| - * |
103 |
| - * @returns {Object} The extracted data |
104 |
| - */ |
105 |
| - function getData (userInfoFields) { |
106 |
| - const content = getContent() |
| 56 | +/** |
| 57 | + * @function |
| 58 | + * @private |
| 59 | + * |
| 60 | + * Get necessary user information out of token content. |
| 61 | + * |
| 62 | + * @param {Object} content The token its content |
| 63 | + * @param {Array.<?string>} [fields] The necessary fields |
| 64 | + * @returns {Object} The collection of requested user info |
| 65 | + */ |
| 66 | +function getUserInfo (content, fields = []) { |
| 67 | + return _.pick(content, ['sub', ...fields]) |
| 68 | +} |
107 | 69 |
|
108 |
| - return { |
109 |
| - expiresIn: getExpiration(content), |
110 |
| - credentials: Object.assign({ |
111 |
| - scope: getScope(content) |
112 |
| - }, getUserInfo(content, userInfoFields)) |
113 |
| - } |
114 |
| - } |
| 70 | +/** |
| 71 | + * @function |
| 72 | + * @public |
| 73 | + * |
| 74 | + * Extract content out of token. |
| 75 | + * The content is the middle part. |
| 76 | + * |
| 77 | + * @param {string} tkn The token to be checked |
| 78 | + * @returns {Object} The token its content |
| 79 | + */ |
| 80 | +function getContent (tkn) { |
| 81 | + return JSON.parse(Buffer.from(tkn.split('.')[1], 'base64').toString()) |
| 82 | +} |
115 | 83 |
|
116 |
| - /** |
117 |
| - * @function |
118 |
| - * @public |
119 |
| - * |
120 |
| - * Get the original token. |
121 |
| - * |
122 |
| - * @returns {string} The original token |
123 |
| - */ |
124 |
| - function get () { |
125 |
| - return tkn |
| 84 | +/** |
| 85 | + * @function |
| 86 | + * @public |
| 87 | + * |
| 88 | + * Get various data out of token content. |
| 89 | + * Get the current scope of the user and |
| 90 | + * when the token expires. |
| 91 | + * |
| 92 | +* @param {string} tkn The token to be checked |
| 93 | + * @returns {Object} The extracted data |
| 94 | + */ |
| 95 | +function getData (tkn, userInfoFields) { |
| 96 | + const content = getContent(tkn) |
| 97 | + |
| 98 | + return { |
| 99 | + expiresIn: getExpiration(content), |
| 100 | + credentials: Object.assign({ |
| 101 | + scope: getScope(content) |
| 102 | + }, getUserInfo(content, userInfoFields)) |
126 | 103 | }
|
| 104 | +} |
127 | 105 |
|
128 |
| - /** |
129 |
| - * @function |
130 |
| - * @private |
131 |
| - * |
132 |
| - * Initialize token instance. |
133 |
| - * |
134 |
| - * @returns {Object|false} The token instance or `false` dependent on field |
135 |
| - */ |
136 |
| - function init () { |
137 |
| - const match = extractToken(field) |
138 |
| - |
139 |
| - if (!match) { |
140 |
| - return false |
141 |
| - } |
142 |
| - |
143 |
| - tkn = match[1] |
144 |
| - |
145 |
| - return { |
146 |
| - getContent, |
147 |
| - getData, |
148 |
| - get |
149 |
| - } |
150 |
| - } |
| 106 | +/** |
| 107 | + * @function |
| 108 | + * @public |
| 109 | + * |
| 110 | + * Get token out of header field. |
| 111 | + * |
| 112 | + * @param {string} field The header field to be scanned |
| 113 | + * @returns {string|false} The token or `false` dependent on field |
| 114 | + */ |
| 115 | +function create (field) { |
| 116 | + const match = extractToken(field) |
151 | 117 |
|
152 |
| - return init() |
| 118 | + return match ? match[1] : false |
153 | 119 | }
|
154 | 120 |
|
155 |
| -module.exports = token |
| 121 | +module.exports = { |
| 122 | + create, |
| 123 | + getContent, |
| 124 | + getData |
| 125 | +} |
0 commit comments