| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 16:56:50 +02:00
										 |  |  | const { castArray, isNil } = require('lodash/fp'); | 
					
						
							| 
									
										
										
										
											2021-10-27 18:54:58 +02:00
										 |  |  | const { UnauthorizedError, ForbiddenError } = require('@strapi/utils').errors; | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | const constants = require('../services/constants'); | 
					
						
							|  |  |  | const { getService } = require('../utils'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-08 23:33:39 +02:00
										 |  |  | const isReadScope = (scope) => scope.endsWith('find') || scope.endsWith('findOne'); | 
					
						
							| 
									
										
										
										
											2021-10-11 09:49:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-08 23:33:39 +02:00
										 |  |  | const extractToken = (ctx) => { | 
					
						
							| 
									
										
										
										
											2021-11-15 17:54:17 +01:00
										 |  |  |   if (ctx.request && ctx.request.header && ctx.request.header.authorization) { | 
					
						
							|  |  |  |     const parts = ctx.request.header.authorization.split(/\s+/); | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-15 17:54:17 +01:00
										 |  |  |     if (parts[0].toLowerCase() !== 'bearer' || parts.length !== 2) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return parts[1]; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-15 17:54:17 +01:00
										 |  |  |   return null; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 09:25:55 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Authenticate the validity of the token | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  @type {import('.').AuthenticateFunction} */ | 
					
						
							| 
									
										
										
										
											2022-08-08 23:33:39 +02:00
										 |  |  | const authenticate = async (ctx) => { | 
					
						
							| 
									
										
										
										
											2021-11-15 17:54:17 +01:00
										 |  |  |   const apiTokenService = getService('api-token'); | 
					
						
							|  |  |  |   const token = extractToken(ctx); | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-15 17:54:17 +01:00
										 |  |  |   if (!token) { | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  |     return { authenticated: false }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const apiToken = await apiTokenService.getBy({ | 
					
						
							|  |  |  |     accessKey: apiTokenService.hash(token), | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 09:25:55 +02:00
										 |  |  |   // token not found
 | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  |   if (!apiToken) { | 
					
						
							|  |  |  |     return { authenticated: false }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 15:44:10 +02:00
										 |  |  |   const currentDate = new Date(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 16:56:50 +02:00
										 |  |  |   if (!isNil(apiToken.expiresAt)) { | 
					
						
							|  |  |  |     const expirationDate = new Date(apiToken.expiresAt); | 
					
						
							|  |  |  |     // token has expired
 | 
					
						
							|  |  |  |     if (expirationDate < currentDate) { | 
					
						
							|  |  |  |       return { authenticated: false, error: new UnauthorizedError('Token expired') }; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-24 10:32:24 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 12:56:31 +02:00
										 |  |  |   // update lastUsedAt
 | 
					
						
							| 
									
										
										
										
											2022-10-03 12:54:19 +02:00
										 |  |  |   await strapi.query('admin::api-token').update({ | 
					
						
							|  |  |  |     where: { id: apiToken.id }, | 
					
						
							|  |  |  |     data: { lastUsedAt: currentDate }, | 
					
						
							| 
									
										
										
										
											2022-08-17 23:28:35 +02:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |   if (apiToken.type === constants.API_TOKEN_TYPE.CUSTOM) { | 
					
						
							|  |  |  |     const ability = await strapi.contentAPI.permissions.engine.generateAbility( | 
					
						
							| 
									
										
										
										
											2022-08-18 12:20:45 +02:00
										 |  |  |       apiToken.permissions.map((action) => ({ action })) | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { authenticated: true, ability, credentials: apiToken }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  |   return { authenticated: true, credentials: apiToken }; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-22 09:25:55 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Verify the token has the required abilities for the requested scope | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  @type {import('.').VerifyFunction} */ | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | const verify = (auth, config) => { | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |   const { credentials: apiToken, ability } = auth; | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (!apiToken) { | 
					
						
							| 
									
										
										
										
											2022-08-24 10:32:24 +02:00
										 |  |  |     throw new UnauthorizedError('Token not found'); | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 15:44:10 +02:00
										 |  |  |   const currentDate = new Date(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-25 16:56:50 +02:00
										 |  |  |   if (!isNil(apiToken.expiresAt)) { | 
					
						
							|  |  |  |     const expirationDate = new Date(apiToken.expiresAt); | 
					
						
							|  |  |  |     // token has expired
 | 
					
						
							|  |  |  |     if (expirationDate < currentDate) { | 
					
						
							|  |  |  |       throw new UnauthorizedError('Token expired'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-22 09:25:55 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |   // Full access
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:07:39 +02:00
										 |  |  |   if (apiToken.type === constants.API_TOKEN_TYPE.FULL_ACCESS) { | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |   // Read only
 | 
					
						
							| 
									
										
										
										
											2022-08-18 12:20:45 +02:00
										 |  |  |   if (apiToken.type === constants.API_TOKEN_TYPE.READ_ONLY) { | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * If you don't have `full-access` you can only access `find` and `findOne` | 
					
						
							|  |  |  |      * scopes. If the route has no scope, then you can't get access to it. | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     const scopes = castArray(config.scope); | 
					
						
							| 
									
										
										
										
											2021-10-11 09:49:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |     if (config.scope && scopes.every(isReadScope)) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Custom
 | 
					
						
							| 
									
										
										
										
											2022-08-11 12:18:53 +02:00
										 |  |  |   else if (apiToken.type === constants.API_TOKEN_TYPE.CUSTOM) { | 
					
						
							|  |  |  |     if (!ability) { | 
					
						
							|  |  |  |       throw new ForbiddenError(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  |     const scopes = castArray(config.scope); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 12:20:45 +02:00
										 |  |  |     const isAllowed = scopes.every((scope) => ability.can(scope)); | 
					
						
							| 
									
										
										
										
											2022-08-05 12:01:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (isAllowed) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-27 18:54:58 +02:00
										 |  |  |   throw new ForbiddenError(); | 
					
						
							| 
									
										
										
										
											2021-09-16 14:36:54 +02:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** @type {import('.').AuthStrategy} */ | 
					
						
							|  |  |  | module.exports = { | 
					
						
							|  |  |  |   name: 'api-token', | 
					
						
							|  |  |  |   authenticate, | 
					
						
							|  |  |  |   verify, | 
					
						
							|  |  |  | }; |