1 | "use strict";
|
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
4 | };
|
5 | Object.defineProperty(exports, "__esModule", { value: true });
|
6 | exports.JWTStrategy = void 0;
|
7 | const errors_1 = require("@feathersjs/errors");
|
8 | const commons_1 = require("@feathersjs/commons");
|
9 |
|
10 | const long_timeout_1 = __importDefault(require("long-timeout"));
|
11 | const strategy_1 = require("./strategy");
|
12 | const debug = (0, commons_1.createDebug)('@feathersjs/authentication/jwt');
|
13 | const SPLIT_HEADER = /(\S+)\s+(\S+)/;
|
14 | class JWTStrategy extends strategy_1.AuthenticationBaseStrategy {
|
15 | constructor() {
|
16 | super(...arguments);
|
17 | this.expirationTimers = new WeakMap();
|
18 | }
|
19 | get configuration() {
|
20 | const authConfig = this.authentication.configuration;
|
21 | const config = super.configuration;
|
22 | return {
|
23 | service: authConfig.service,
|
24 | entity: authConfig.entity,
|
25 | entityId: authConfig.entityId,
|
26 | header: 'Authorization',
|
27 | schemes: ['Bearer', 'JWT'],
|
28 | ...config
|
29 | };
|
30 | }
|
31 | async handleConnection(event, connection, authResult) {
|
32 | var _a;
|
33 | const isValidLogout = event === 'logout' &&
|
34 | connection.authentication &&
|
35 | authResult &&
|
36 | connection.authentication.accessToken === authResult.accessToken;
|
37 | const { accessToken } = authResult || {};
|
38 | const { entity } = this.configuration;
|
39 | if (accessToken && event === 'login') {
|
40 | debug('Adding authentication information to connection');
|
41 | const { exp } = ((_a = authResult === null || authResult === void 0 ? void 0 : authResult.authentication) === null || _a === void 0 ? void 0 : _a.payload) || (await this.authentication.verifyAccessToken(accessToken));
|
42 |
|
43 | const duration = exp * 1000 - Date.now();
|
44 | const timer = long_timeout_1.default.setTimeout(() => this.app.emit('disconnect', connection), duration);
|
45 | debug(`Registering connection expiration timer for ${duration}ms`);
|
46 | long_timeout_1.default.clearTimeout(this.expirationTimers.get(connection));
|
47 | this.expirationTimers.set(connection, timer);
|
48 | debug('Adding authentication information to connection');
|
49 | connection.authentication = {
|
50 | strategy: this.name,
|
51 | accessToken
|
52 | };
|
53 | connection[entity] = authResult[entity];
|
54 | }
|
55 | else if (event === 'disconnect' || isValidLogout) {
|
56 | debug('Removing authentication information and expiration timer from connection');
|
57 | await new Promise((resolve) => process.nextTick(() => {
|
58 | delete connection[entity];
|
59 | delete connection.authentication;
|
60 | resolve(connection);
|
61 | }));
|
62 | long_timeout_1.default.clearTimeout(this.expirationTimers.get(connection));
|
63 | this.expirationTimers.delete(connection);
|
64 | }
|
65 | }
|
66 | verifyConfiguration() {
|
67 | const allowedKeys = ['entity', 'entityId', 'service', 'header', 'schemes'];
|
68 | for (const key of Object.keys(this.configuration)) {
|
69 | if (!allowedKeys.includes(key)) {
|
70 | throw new Error(`Invalid JwtStrategy option 'authentication.${this.name}.${key}'. Did you mean to set it in 'authentication.jwtOptions'?`);
|
71 | }
|
72 | }
|
73 | if (typeof this.configuration.header !== 'string') {
|
74 | throw new Error(`The 'header' option for the ${this.name} strategy must be a string`);
|
75 | }
|
76 | }
|
77 | async getEntityQuery(_params) {
|
78 | return {};
|
79 | }
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | async getEntity(id, params) {
|
87 | const entityService = this.entityService;
|
88 | const { entity } = this.configuration;
|
89 | debug('Getting entity', id);
|
90 | if (entityService === null) {
|
91 | throw new errors_1.NotAuthenticated('Could not find entity service');
|
92 | }
|
93 | const query = await this.getEntityQuery(params);
|
94 | const { provider, ...paramsWithoutProvider } = params;
|
95 | const result = await entityService.get(id, {
|
96 | ...paramsWithoutProvider,
|
97 | query
|
98 | });
|
99 | if (!params.provider) {
|
100 | return result;
|
101 | }
|
102 | return entityService.get(id, { ...params, [entity]: result });
|
103 | }
|
104 | async getEntityId(authResult, _params) {
|
105 | return authResult.authentication.payload.sub;
|
106 | }
|
107 | async authenticate(authentication, params) {
|
108 | const { accessToken } = authentication;
|
109 | const { entity } = this.configuration;
|
110 | if (!accessToken) {
|
111 | throw new errors_1.NotAuthenticated('No access token');
|
112 | }
|
113 | const payload = await this.authentication.verifyAccessToken(accessToken, params.jwt);
|
114 | const result = {
|
115 | accessToken,
|
116 | authentication: {
|
117 | strategy: 'jwt',
|
118 | accessToken,
|
119 | payload
|
120 | }
|
121 | };
|
122 | if (entity === null) {
|
123 | return result;
|
124 | }
|
125 | const entityId = await this.getEntityId(result, params);
|
126 | const value = await this.getEntity(entityId, params);
|
127 | return {
|
128 | ...result,
|
129 | [entity]: value
|
130 | };
|
131 | }
|
132 | async parse(req) {
|
133 | const { header, schemes } = this.configuration;
|
134 | const headerValue = req.headers && req.headers[header.toLowerCase()];
|
135 | if (!headerValue || typeof headerValue !== 'string') {
|
136 | return null;
|
137 | }
|
138 | debug('Found parsed header value');
|
139 | const [, scheme, schemeValue] = headerValue.match(SPLIT_HEADER) || [];
|
140 | const hasScheme = scheme && schemes.some((current) => new RegExp(current, 'i').test(scheme));
|
141 | if (scheme && !hasScheme) {
|
142 | return null;
|
143 | }
|
144 | return {
|
145 | strategy: this.name,
|
146 | accessToken: hasScheme ? schemeValue : headerValue
|
147 | };
|
148 | }
|
149 | }
|
150 | exports.JWTStrategy = JWTStrategy;
|
151 |
|
\ | No newline at end of file |