| import { GlobalConfig } from '@n8n/config'; |
| import { Service } from '@n8n/di'; |
| import { randomBytes, timingSafeEqual } from 'crypto'; |
|
|
| import { Time } from '@/constants'; |
| import { CacheService } from '@/services/cache/cache.service'; |
|
|
| const GRANT_TOKEN_TTL = 15 * Time.seconds.toMilliseconds; |
|
|
| @Service() |
| export class TaskBrokerAuthService { |
| private readonly authToken = Buffer.from(this.globalConfig.taskRunners.authToken); |
|
|
| constructor( |
| private readonly globalConfig: GlobalConfig, |
| private readonly cacheService: CacheService, |
| |
| private readonly grantTokenTtl = GRANT_TOKEN_TTL, |
| ) {} |
|
|
| isValidAuthToken(token: string) { |
| const tokenBuffer = Buffer.from(token); |
| if (tokenBuffer.length !== this.authToken.length) return false; |
|
|
| return timingSafeEqual(tokenBuffer, this.authToken); |
| } |
|
|
| |
| |
| |
| async createGrantToken() { |
| const grantToken = this.generateGrantToken(); |
|
|
| const key = this.cacheKeyForGrantToken(grantToken); |
| await this.cacheService.set(key, '1', this.grantTokenTtl); |
|
|
| return grantToken; |
| } |
|
|
| |
| |
| |
| |
| async tryConsumeGrantToken(grantToken: string) { |
| const key = this.cacheKeyForGrantToken(grantToken); |
| const consumed = await this.cacheService.get<string>(key); |
| |
| if (consumed === undefined) return false; |
|
|
| await this.cacheService.delete(key); |
| return true; |
| } |
|
|
| private generateGrantToken() { |
| return randomBytes(32).toString('hex'); |
| } |
|
|
| private cacheKeyForGrantToken(grantToken: string) { |
| return `grant-token:${grantToken}`; |
| } |
| } |
|
|