1- import type { Repository } from 'typeorm' ;
1+ import type { FindOptionsOrder , FindOptionsRelations , FindOptionsWhere , Repository } from 'typeorm' ;
2+ import type { CustomerEntity } from '../../database/entities/customer.entity.js' ;
3+ import type { UserEntity } from '../../database/entities/user.entity.js' ;
4+ import type { APIServiceOptions } from '../../types/admin.js' ;
25import { decodeJWT } from 'did-jwt' ;
36import bcrypt from 'bcrypt' ;
47import { randomBytes , createHmac } from 'crypto' ;
8+ import { SecretBox } from '@veramo/kms-local' ;
59import { Connection } from '../../database/connection/connection.js' ;
6-
7- import * as dotenv from 'dotenv' ;
8- import type { CustomerEntity } from '../../database/entities/customer.entity.js' ;
910import { APIKeyEntity } from '../../database/entities/api.key.entity.js' ;
10- import type { UserEntity } from '../../database/entities/user.entity.js' ;
11- import { SecretBox } from '@veramo/kms-local' ;
1211import { API_SECRET_KEY_LENGTH , API_KEY_PREFIX , API_KEY_EXPIRATION } from '../../types/constants.js' ;
13- import type { APIServiceOptions } from '../../types/admin.js' ;
12+ import { sha256 } from '../../utils/index.js' ;
13+ import * as dotenv from 'dotenv' ;
1414dotenv . config ( ) ;
1515
1616export class APIKeyService {
@@ -35,11 +35,17 @@ export class APIKeyService {
3535 revoked = false ,
3636 options ?: APIServiceOptions
3737 ) : Promise < APIKeyEntity > {
38- const apiKeyHash = await APIKeyService . hashAPIKey ( apiKey ) ;
3938 const { decryptionNeeded } = options || { } ;
4039 if ( ! apiKey ) {
4140 throw new Error ( 'API key is not specified' ) ;
4241 }
42+
43+ // fingerprint
44+ const fingerprint = sha256 ( apiKey ) ;
45+
46+ // slow - hash
47+ const apiKeyHash = await APIKeyService . hashAPIKey ( apiKey ) ;
48+
4349 if ( ! name ) {
4450 throw new Error ( 'API key name is not specified' ) ;
4551 }
@@ -50,7 +56,10 @@ export class APIKeyService {
5056 expiresAt = new Date ( ) ;
5157 expiresAt . setMonth ( expiresAt . getDay ( ) + API_KEY_EXPIRATION ) ;
5258 }
59+
60+ // encrypt the key
5361 const encryptedAPIKey = await this . encryptAPIKey ( apiKey ) ;
62+
5463 // Create entity
5564 const apiKeyEntity = new APIKeyEntity (
5665 apiKeyHash ,
@@ -59,7 +68,8 @@ export class APIKeyService {
5968 expiresAt ,
6069 user . customer ,
6170 user ,
62- revoked
71+ revoked ,
72+ fingerprint
6373 ) ;
6474 const apiKeyRecord = ( await this . apiKeyRepository . insert ( apiKeyEntity ) ) . identifiers [ 0 ] ;
6575 if ( ! apiKeyRecord ) throw new Error ( `Cannot create a new API key` ) ;
@@ -131,31 +141,45 @@ export class APIKeyService {
131141 }
132142
133143 public async get ( apiKey : string , options ?: APIServiceOptions ) {
134- const { decryptionNeeded } = options || { } ;
144+ // fingerprint
145+ const fingerprint = sha256 ( apiKey ) ;
135146
136- // ToDo: possible bottleneck cause we are fetching all the keys
137- for ( const record of await this . find ( { } ) ) {
138- if ( await APIKeyService . compareAPIKey ( apiKey , record . apiKeyHash ) ) {
139- if ( decryptionNeeded ) {
140- record . apiKey = await this . decryptAPIKey ( record . apiKey ) ;
141- }
142- return record ;
143- }
147+ // fetch the api key entity
148+ const apiKeyEntity = await APIKeyService . instance . findOne (
149+ {
150+ fingerprint,
151+ } ,
152+ { customer : true , user : true } ,
153+ options
154+ ) ;
155+ if ( ! apiKeyEntity ) {
156+ throw new Error ( 'Invalid API key' ) ;
157+ }
158+
159+ // validate expiry
160+ if ( apiKeyEntity . revoked ) {
161+ throw new Error ( 'API Key is expired' ) ;
144162 }
145- return null ;
163+
164+ // bcrypt comparison
165+ const isValid = await APIKeyService . compareAPIKey ( apiKey , apiKeyEntity . apiKeyHash ) ;
166+ if ( ! isValid ) throw new Error ( 'Invalid API key' ) ;
167+
168+ return apiKeyEntity ;
146169 }
147170
148171 public async find (
149- where : Record < string , unknown > ,
150- order ?: Record < string , 'ASC' | 'DESC' > ,
151- options ?: APIServiceOptions
172+ where : FindOptionsWhere < APIKeyEntity > ,
173+ relations ?: FindOptionsRelations < APIKeyEntity > ,
174+ options ?: APIServiceOptions ,
175+ order ?: FindOptionsOrder < APIKeyEntity >
152176 ) {
153177 try {
154178 const { decryptionNeeded } = options || { } ;
155179 const apiKeyList = await this . apiKeyRepository . find ( {
156- where : where ,
157- relations : [ 'customer' , 'user' ] ,
158- order : order ,
180+ where,
181+ relations,
182+ order,
159183 } ) ;
160184 if ( decryptionNeeded ) {
161185 for ( const apiKey of apiKeyList ) {
@@ -168,6 +192,25 @@ export class APIKeyService {
168192 }
169193 }
170194
195+ public async findOne (
196+ where : FindOptionsWhere < APIKeyEntity > ,
197+ relations ?: FindOptionsRelations < APIKeyEntity > ,
198+ options ?: APIServiceOptions ,
199+ order ?: FindOptionsOrder < APIKeyEntity >
200+ ) {
201+ const apiKeyEntity = await this . apiKeyRepository . findOne ( {
202+ where,
203+ relations,
204+ order,
205+ } ) ;
206+
207+ if ( apiKeyEntity && options ?. decryptionNeeded ) {
208+ apiKeyEntity . apiKey = await this . decryptAPIKey ( apiKeyEntity . apiKey ) ;
209+ }
210+
211+ return apiKeyEntity ;
212+ }
213+
171214 // Utils
172215 public static generateAPIKey ( userId : string ) : string {
173216 const apiKey = createHmac ( 'sha512' , randomBytes ( API_SECRET_KEY_LENGTH ) . toString ( 'hex' ) )
0 commit comments