diff --git a/backend/package.json b/backend/package.json index 08e4bc2..ca2f3a4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -41,6 +41,7 @@ "ms": "^2.1.3", "p-timeout": "^6.0.0", "passport": "^0.6.0", + "passport-headerapikey": "^1.2.2", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "passport-strategy": "^1.0.0", diff --git a/backend/src/managers/auth/auth.module.ts b/backend/src/managers/auth/auth.module.ts index 4a5dcec..79fedea 100644 --- a/backend/src/managers/auth/auth.module.ts +++ b/backend/src/managers/auth/auth.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { PassportModule } from '@nestjs/passport'; +import { ApiKeyDbModule } from '../../collections/apikey-db/apikey-db.module'; import { PreferenceDbModule } from '../../collections/preference-db/preference-db.module'; import { UserDbModule } from '../../collections/user-db/user-db.module'; import { @@ -9,6 +10,7 @@ import { } from '../../config/late/jwt.config.service'; import { LateConfigModule } from '../../config/late/late-config.module'; import { AuthManagerService } from './auth.service'; +import { ApiKeyStrategy } from './guards/apikey.strategy'; import { GuestStrategy } from './guards/guest.strategy'; import { JwtStrategy } from './guards/jwt.strategy'; import { LocalAuthStrategy } from './guards/local-auth.strategy'; @@ -19,6 +21,7 @@ import { GuestService } from './guest.service'; UserDbModule, PassportModule, PreferenceDbModule, + ApiKeyDbModule, LateConfigModule, JwtModule.registerAsync({ useExisting: JwtConfigService, @@ -31,6 +34,7 @@ import { GuestService } from './guest.service'; JwtStrategy, GuestStrategy, JwtSecretProvider, + ApiKeyStrategy, GuestService, ], exports: [UserDbModule, AuthManagerService], diff --git a/backend/src/managers/auth/guards/apikey.strategy.ts b/backend/src/managers/auth/guards/apikey.strategy.ts new file mode 100644 index 0000000..dd486d0 --- /dev/null +++ b/backend/src/managers/auth/guards/apikey.strategy.ts @@ -0,0 +1,62 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { HeaderAPIKeyStrategy } from 'passport-headerapikey'; +import { EUser, EUserSchema } from 'picsur-shared/dist/entities/user.entity'; +import { HasFailed } from 'picsur-shared/dist/types'; +import { IsApiKey } from 'picsur-shared/dist/validators/api-key.validator'; +import { ApiKeyDbService } from '../../../collections/apikey-db/apikey-db.service'; +import { EUserBackend2EUser } from '../../../models/transformers/user.transformer'; + +@Injectable() +export class ApiKeyStrategy extends PassportStrategy( + HeaderAPIKeyStrategy, + 'apikey', +) { + private readonly logger = new Logger(ApiKeyStrategy.name); + + constructor(private readonly apikeyDB: ApiKeyDbService) { + super( + { + header: 'Authorization', + prefix: 'Api-Key ', + }, + false, + ( + apikey: string, + verified: (err: Error | null, user?: Object, info?: Object) => void, + ) => { + this.validate(apikey) + .then((user) => { + verified(null, user === false ? undefined : user); + }) + .catch((err) => { + verified(err, undefined); + }); + }, + ); + } + + async validate(apikey: string): Promise { + const apiValidation = await IsApiKey().safeParseAsync(apikey); + if (!apiValidation.success) { + this.logger.warn('Invalid apikey format: ' + apikey); + return false; + } + + const apikeyResult = await this.apikeyDB.resolve(apikey); + if (HasFailed(apikeyResult)) { + this.logger.warn('Invalid apikey: ' + apikey); + return false; + } + + const user = EUserBackend2EUser(apikeyResult.user); + + const userValidation = await EUserSchema.safeParseAsync(user); + if (!userValidation.success) { + this.logger.error('Invalid user: ' + JSON.stringify(user)); + return false; + } + + return userValidation.data; + } +} diff --git a/backend/src/managers/auth/guards/main.guard.ts b/backend/src/managers/auth/guards/main.guard.ts index 518e846..ec76e4d 100644 --- a/backend/src/managers/auth/guards/main.guard.ts +++ b/backend/src/managers/auth/guards/main.guard.ts @@ -12,7 +12,7 @@ import { isPermissionsArray } from '../../../models/validators/permissions.valid // This way a user will get his own account when logged in, but received guest permissions when not @Injectable() -export class MainAuthGuard extends AuthGuard(['jwt', 'guest']) { +export class MainAuthGuard extends AuthGuard(['apikey', 'jwt', 'guest']) { private readonly logger = new Logger(MainAuthGuard.name); constructor( diff --git a/backend/src/routes/api/experiment/experiment.controller.ts b/backend/src/routes/api/experiment/experiment.controller.ts index a960ff9..86c84de 100644 --- a/backend/src/routes/api/experiment/experiment.controller.ts +++ b/backend/src/routes/api/experiment/experiment.controller.ts @@ -20,10 +20,6 @@ export class ExperimentController { @Request() req: AuthFasityRequest, @ReqUserID() thing: string, ): Promise { - const key = await this.apikeyDB.findOne("0SB7nCIkfhnAmf3Glejf0naUbI7dimhh", undefined); - - console.log(key); - return req.user; } } diff --git a/yarn.lock b/yarn.lock index a8ab28f..6e46985 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8741,6 +8741,16 @@ __metadata: languageName: node linkType: hard +"passport-headerapikey@npm:^1.2.2": + version: 1.2.2 + resolution: "passport-headerapikey@npm:1.2.2" + dependencies: + lodash: ^4.17.15 + passport-strategy: ^1.0.0 + checksum: 509b0d72225845fcb4d6a95acd76f98042bdee256bf11731ece2c8be8341235013f2150207148d7e8ed939e28857dea18503969842ad40bba7a9724396b94481 + languageName: node + linkType: hard + "passport-jwt@npm:^4.0.0": version: 4.0.0 resolution: "passport-jwt@npm:4.0.0" @@ -8991,6 +9001,7 @@ __metadata: ms: ^2.1.3 p-timeout: ^6.0.0 passport: ^0.6.0 + passport-headerapikey: ^1.2.2 passport-jwt: ^4.0.0 passport-local: ^1.0.0 passport-strategy: ^1.0.0