mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
refactor(server): api keys (#1339)
* refactor: api keys * refactor: test module * chore: tests * chore: fix provider * refactor: test mock repos
This commit is contained in:
parent
0c469cc712
commit
92972ac776
@ -1,16 +0,0 @@
|
|||||||
import { APIKeyEntity } from '@app/infra';
|
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { APIKeyController } from './api-key.controller';
|
|
||||||
import { APIKeyRepository, IKeyRepository } from './api-key.repository';
|
|
||||||
import { APIKeyService } from './api-key.service';
|
|
||||||
|
|
||||||
const KEY_REPOSITORY = { provide: IKeyRepository, useClass: APIKeyRepository };
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [TypeOrmModule.forFeature([APIKeyEntity])],
|
|
||||||
controllers: [APIKeyController],
|
|
||||||
providers: [APIKeyService, KEY_REPOSITORY],
|
|
||||||
exports: [APIKeyService, KEY_REPOSITORY],
|
|
||||||
})
|
|
||||||
export class APIKeyModule {}
|
|
@ -2,7 +2,6 @@ import { immichAppConfig, immichBullAsyncConfig } from '@app/common/config';
|
|||||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||||
import { AssetModule } from './api-v1/asset/asset.module';
|
import { AssetModule } from './api-v1/asset/asset.module';
|
||||||
import { AuthModule } from './api-v1/auth/auth.module';
|
import { AuthModule } from './api-v1/auth/auth.module';
|
||||||
import { APIKeyModule } from './api-v1/api-key/api-key.module';
|
|
||||||
import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module';
|
import { ImmichJwtModule } from './modules/immich-jwt/immich-jwt.module';
|
||||||
import { DeviceInfoModule } from './api-v1/device-info/device-info.module';
|
import { DeviceInfoModule } from './api-v1/device-info/device-info.module';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
@ -22,7 +21,7 @@ import { ImmichConfigModule } from '@app/immich-config';
|
|||||||
import { ShareModule } from './api-v1/share/share.module';
|
import { ShareModule } from './api-v1/share/share.module';
|
||||||
import { DomainModule } from '@app/domain';
|
import { DomainModule } from '@app/domain';
|
||||||
import { InfraModule } from '@app/infra';
|
import { InfraModule } from '@app/infra';
|
||||||
import { UserController } from './controllers';
|
import { APIKeyController, UserController } from './controllers';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -32,8 +31,6 @@ import { UserController } from './controllers';
|
|||||||
imports: [InfraModule],
|
imports: [InfraModule],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
APIKeyModule,
|
|
||||||
|
|
||||||
AssetModule,
|
AssetModule,
|
||||||
|
|
||||||
AuthModule,
|
AuthModule,
|
||||||
@ -69,6 +66,7 @@ import { UserController } from './controllers';
|
|||||||
controllers: [
|
controllers: [
|
||||||
//
|
//
|
||||||
AppController,
|
AppController,
|
||||||
|
APIKeyController,
|
||||||
UserController,
|
UserController,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
import {
|
||||||
|
APIKeyCreateDto,
|
||||||
|
APIKeyCreateResponseDto,
|
||||||
|
APIKeyResponseDto,
|
||||||
|
APIKeyService,
|
||||||
|
APIKeyUpdateDto,
|
||||||
|
AuthUserDto,
|
||||||
|
} from '@app/domain';
|
||||||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, ValidationPipe } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, ValidationPipe } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
|
import { GetAuthUser } from '../decorators/auth-user.decorator';
|
||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||||
import { APIKeyService } from './api-key.service';
|
|
||||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
|
||||||
import { APIKeyUpdateDto } from './dto/api-key-update.dto';
|
|
||||||
import { APIKeyCreateResponseDto } from './repsonse-dto/api-key-create-response.dto';
|
|
||||||
import { APIKeyResponseDto } from './repsonse-dto/api-key-response.dto';
|
|
||||||
|
|
||||||
@ApiTags('API Key')
|
@ApiTags('API Key')
|
||||||
@Controller('api-key')
|
@Controller('api-key')
|
@ -1 +1,2 @@
|
|||||||
|
export * from './api-key.controller';
|
||||||
export * from './user.controller';
|
export * from './user.controller';
|
||||||
|
@ -3,13 +3,12 @@ import { ImmichJwtService } from './immich-jwt.service';
|
|||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { jwtConfig } from '../../config/jwt.config';
|
import { jwtConfig } from '../../config/jwt.config';
|
||||||
import { JwtStrategy } from './strategies/jwt.strategy';
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||||
import { APIKeyModule } from '../../api-v1/api-key/api-key.module';
|
|
||||||
import { APIKeyStrategy } from './strategies/api-key.strategy';
|
import { APIKeyStrategy } from './strategies/api-key.strategy';
|
||||||
import { ShareModule } from '../../api-v1/share/share.module';
|
import { ShareModule } from '../../api-v1/share/share.module';
|
||||||
import { PublicShareStrategy } from './strategies/public-share.strategy';
|
import { PublicShareStrategy } from './strategies/public-share.strategy';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [JwtModule.register(jwtConfig), APIKeyModule, ShareModule],
|
imports: [JwtModule.register(jwtConfig), ShareModule],
|
||||||
providers: [ImmichJwtService, JwtStrategy, APIKeyStrategy, PublicShareStrategy],
|
providers: [ImmichJwtService, JwtStrategy, APIKeyStrategy, PublicShareStrategy],
|
||||||
exports: [ImmichJwtService],
|
exports: [ImmichJwtService],
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
import { APIKeyService, AuthUserDto } from '@app/domain';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { IStrategyOptions, Strategy } from 'passport-http-header-strategy';
|
import { IStrategyOptions, Strategy } from 'passport-http-header-strategy';
|
||||||
import { APIKeyService } from '../../../api-v1/api-key/api-key.service';
|
|
||||||
import { AuthUserDto } from '../../../decorators/auth-user.decorator';
|
|
||||||
|
|
||||||
export const API_KEY_STRATEGY = 'api-key';
|
export const API_KEY_STRATEGY = 'api-key';
|
||||||
|
|
||||||
@ -16,16 +15,7 @@ export class APIKeyStrategy extends PassportStrategy(Strategy, API_KEY_STRATEGY)
|
|||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(token: string): Promise<AuthUserDto> {
|
validate(token: string): Promise<AuthUserDto> {
|
||||||
const user = await this.apiKeyService.validate(token);
|
return this.apiKeyService.validate(token);
|
||||||
|
|
||||||
const authUser = new AuthUserDto();
|
|
||||||
authUser.id = user.id;
|
|
||||||
authUser.email = user.email;
|
|
||||||
authUser.isAdmin = user.isAdmin;
|
|
||||||
authUser.isPublicUser = false;
|
|
||||||
authUser.isAllowUpload = true;
|
|
||||||
|
|
||||||
return authUser;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,6 @@
|
|||||||
"declaration": false,
|
"declaration": false,
|
||||||
"outDir": "../../dist/apps/immich"
|
"outDir": "../../dist/apps/immich"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*",
|
"exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
|
||||||
"../../libs/**/*"
|
}
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
"test",
|
|
||||||
"**/*spec.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,153 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.0",
|
"openapi": "3.0.0",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/api-key": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "createKey",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyCreateDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyCreateResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"API Key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"operationId": "getKeys",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"API Key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api-key/{id}": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "getKey",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"API Key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"operationId": "updateKey",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyUpdateDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"API Key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "deleteKey",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"in": "path",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"API Key"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user": {
|
"/user": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getAllUsers",
|
"operationId": "getAllUsers",
|
||||||
@ -341,153 +488,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api-key": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "createKey",
|
|
||||||
"description": "",
|
|
||||||
"parameters": [],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyCreateDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyCreateResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"API Key"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"get": {
|
|
||||||
"operationId": "getKeys",
|
|
||||||
"description": "",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"API Key"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api-key/{id}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "getKey",
|
|
||||||
"description": "",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"API Key"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"operationId": "updateKey",
|
|
||||||
"description": "",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyUpdateDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"API Key"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"operationId": "deleteKey",
|
|
||||||
"description": "",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"API Key"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/asset/upload": {
|
"/asset/upload": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "uploadFile",
|
"operationId": "uploadFile",
|
||||||
@ -2825,6 +2825,63 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schemas": {
|
"schemas": {
|
||||||
|
"APIKeyCreateDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"APIKeyResponseDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"APIKeyCreateResponseDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"secret": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"apiKey": {
|
||||||
|
"$ref": "#/components/schemas/APIKeyResponseDto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"secret",
|
||||||
|
"apiKey"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"APIKeyUpdateDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
"UserResponseDto": {
|
"UserResponseDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -2969,63 +3026,6 @@
|
|||||||
"profileImagePath"
|
"profileImagePath"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"APIKeyCreateDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"APIKeyResponseDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"createdAt",
|
|
||||||
"updatedAt"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"APIKeyCreateResponseDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"secret": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"apiKey": {
|
|
||||||
"$ref": "#/components/schemas/APIKeyResponseDto"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"secret",
|
|
||||||
"apiKey"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"APIKeyUpdateDto": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"AssetFileUploadDto": {
|
"AssetFileUploadDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
16
server/libs/domain/src/api-key/api-key.repository.ts
Normal file
16
server/libs/domain/src/api-key/api-key.repository.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { APIKeyEntity } from '@app/infra';
|
||||||
|
|
||||||
|
export const IKeyRepository = 'IKeyRepository';
|
||||||
|
|
||||||
|
export interface IKeyRepository {
|
||||||
|
create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
||||||
|
update(userId: string, id: number, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
||||||
|
delete(userId: string, id: number): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Includes the hashed `key` for verification
|
||||||
|
* @param id
|
||||||
|
*/
|
||||||
|
getKey(id: number): Promise<APIKeyEntity | null>;
|
||||||
|
getById(userId: string, id: number): Promise<APIKeyEntity | null>;
|
||||||
|
getByUserId(userId: string): Promise<APIKeyEntity[]>;
|
||||||
|
}
|
142
server/libs/domain/src/api-key/api-key.service.spec.ts
Normal file
142
server/libs/domain/src/api-key/api-key.service.spec.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { APIKeyEntity } from '@app/infra';
|
||||||
|
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { authStub, entityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
||||||
|
import { ICryptoRepository } from '../auth';
|
||||||
|
import { IKeyRepository } from './api-key.repository';
|
||||||
|
import { APIKeyService } from './api-key.service';
|
||||||
|
|
||||||
|
const adminKey = Object.freeze({
|
||||||
|
id: 1,
|
||||||
|
name: 'My Key',
|
||||||
|
key: 'my-api-key (hashed)',
|
||||||
|
userId: authStub.admin.id,
|
||||||
|
user: entityStub.admin,
|
||||||
|
} as APIKeyEntity);
|
||||||
|
|
||||||
|
const token = Buffer.from('1:my-api-key', 'utf8').toString('base64');
|
||||||
|
|
||||||
|
describe(APIKeyService.name, () => {
|
||||||
|
let sut: APIKeyService;
|
||||||
|
let keyMock: jest.Mocked<IKeyRepository>;
|
||||||
|
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
cryptoMock = newCryptoRepositoryMock();
|
||||||
|
keyMock = newKeyRepositoryMock();
|
||||||
|
sut = new APIKeyService(cryptoMock, keyMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a new key', async () => {
|
||||||
|
keyMock.create.mockResolvedValue(adminKey);
|
||||||
|
|
||||||
|
await sut.create(authStub.admin, { name: 'Test Key' });
|
||||||
|
|
||||||
|
expect(keyMock.create).toHaveBeenCalledWith({
|
||||||
|
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||||
|
name: 'Test Key',
|
||||||
|
userId: authStub.admin.id,
|
||||||
|
});
|
||||||
|
expect(cryptoMock.randomBytes).toHaveBeenCalled();
|
||||||
|
expect(cryptoMock.hash).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not require a name', async () => {
|
||||||
|
keyMock.create.mockResolvedValue(adminKey);
|
||||||
|
|
||||||
|
await sut.create(authStub.admin, {});
|
||||||
|
|
||||||
|
expect(keyMock.create).toHaveBeenCalledWith({
|
||||||
|
key: 'cmFuZG9tLWJ5dGVz (hashed)',
|
||||||
|
name: 'API Key',
|
||||||
|
userId: authStub.admin.id,
|
||||||
|
});
|
||||||
|
expect(cryptoMock.randomBytes).toHaveBeenCalled();
|
||||||
|
expect(cryptoMock.hash).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should throw an error if the key is not found', async () => {
|
||||||
|
keyMock.getById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(sut.update(authStub.admin, 1, { name: 'New Name' })).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
|
expect(keyMock.update).not.toHaveBeenCalledWith(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a key', async () => {
|
||||||
|
keyMock.getById.mockResolvedValue(adminKey);
|
||||||
|
|
||||||
|
await sut.update(authStub.admin, 1, { name: 'New Name' });
|
||||||
|
|
||||||
|
expect(keyMock.update).toHaveBeenCalledWith(authStub.admin.id, 1, { name: 'New Name' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should throw an error if the key is not found', async () => {
|
||||||
|
keyMock.getById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(sut.delete(authStub.admin, 1)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
|
expect(keyMock.delete).not.toHaveBeenCalledWith(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a key', async () => {
|
||||||
|
keyMock.getById.mockResolvedValue(adminKey);
|
||||||
|
|
||||||
|
await sut.delete(authStub.admin, 1);
|
||||||
|
|
||||||
|
expect(keyMock.delete).toHaveBeenCalledWith(authStub.admin.id, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getById', () => {
|
||||||
|
it('should throw an error if the key is not found', async () => {
|
||||||
|
keyMock.getById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(sut.getById(authStub.admin, 1)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
|
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get a key by id', async () => {
|
||||||
|
keyMock.getById.mockResolvedValue(adminKey);
|
||||||
|
|
||||||
|
await sut.getById(authStub.admin, 1);
|
||||||
|
|
||||||
|
expect(keyMock.getById).toHaveBeenCalledWith(authStub.admin.id, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAll', () => {
|
||||||
|
it('should return all the keys for a user', async () => {
|
||||||
|
keyMock.getByUserId.mockResolvedValue([adminKey]);
|
||||||
|
|
||||||
|
await expect(sut.getAll(authStub.admin)).resolves.toHaveLength(1);
|
||||||
|
|
||||||
|
expect(keyMock.getByUserId).toHaveBeenCalledWith(authStub.admin.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validate', () => {
|
||||||
|
it('should throw an error for an invalid id', async () => {
|
||||||
|
keyMock.getKey.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(sut.validate(token)).rejects.toBeInstanceOf(UnauthorizedException);
|
||||||
|
|
||||||
|
expect(keyMock.getKey).toHaveBeenCalledWith(1);
|
||||||
|
expect(cryptoMock.compareSync).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate the token', async () => {
|
||||||
|
keyMock.getKey.mockResolvedValue(adminKey);
|
||||||
|
|
||||||
|
await expect(sut.validate(token)).resolves.toEqual(authStub.admin);
|
||||||
|
|
||||||
|
expect(keyMock.getKey).toHaveBeenCalledWith(1);
|
||||||
|
expect(cryptoMock.compareSync).toHaveBeenCalledWith('my-api-key', 'my-api-key (hashed)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,21 +1,22 @@
|
|||||||
import { UserEntity } from '@app/infra';
|
import { UserEntity } from '@app/infra';
|
||||||
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { compareSync, hash } from 'bcrypt';
|
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||||
import { randomBytes } from 'node:crypto';
|
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
|
||||||
import { IKeyRepository } from './api-key.repository';
|
import { IKeyRepository } from './api-key.repository';
|
||||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
||||||
import { APIKeyCreateResponseDto } from './repsonse-dto/api-key-create-response.dto';
|
import { APIKeyCreateResponseDto } from './response-dto/api-key-create-response.dto';
|
||||||
import { APIKeyResponseDto, mapKey } from './repsonse-dto/api-key-response.dto';
|
import { APIKeyResponseDto, mapKey } from './response-dto/api-key-response.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class APIKeyService {
|
export class APIKeyService {
|
||||||
constructor(@Inject(IKeyRepository) private repository: IKeyRepository) {}
|
constructor(
|
||||||
|
@Inject(ICryptoRepository) private crypto: ICryptoRepository,
|
||||||
|
@Inject(IKeyRepository) private repository: IKeyRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
async create(authUser: AuthUserDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
async create(authUser: AuthUserDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||||
const key = randomBytes(24).toString('base64').replace(/\W/g, '');
|
const key = this.crypto.randomBytes(24).toString('base64').replace(/\W/g, '');
|
||||||
const entity = await this.repository.create({
|
const entity = await this.repository.create({
|
||||||
key: await hash(key, 10),
|
key: await this.crypto.hash(key, 10),
|
||||||
name: dto.name || 'API Key',
|
name: dto.name || 'API Key',
|
||||||
userId: authUser.id,
|
userId: authUser.id,
|
||||||
});
|
});
|
||||||
@ -58,14 +59,22 @@ export class APIKeyService {
|
|||||||
return keys.map(mapKey);
|
return keys.map(mapKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(token: string): Promise<UserEntity> {
|
async validate(token: string): Promise<AuthUserDto> {
|
||||||
const [_id, key] = Buffer.from(token, 'base64').toString('utf8').split(':');
|
const [_id, key] = Buffer.from(token, 'base64').toString('utf8').split(':');
|
||||||
const id = Number(_id);
|
const id = Number(_id);
|
||||||
|
|
||||||
if (id && key) {
|
if (id && key) {
|
||||||
const entity = await this.repository.getKey(id);
|
const entity = await this.repository.getKey(id);
|
||||||
if (entity?.user && entity?.key && compareSync(key, entity.key)) {
|
if (entity?.user && entity?.key && this.crypto.compareSync(key, entity.key)) {
|
||||||
return entity.user as UserEntity;
|
const user = entity.user as UserEntity;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
isAdmin: user.isAdmin,
|
||||||
|
isPublicUser: false,
|
||||||
|
isAllowUpload: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
server/libs/domain/src/api-key/dto/index.ts
Normal file
2
server/libs/domain/src/api-key/dto/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './api-key-create.dto';
|
||||||
|
export * from './api-key-update.dto';
|
4
server/libs/domain/src/api-key/index.ts
Normal file
4
server/libs/domain/src/api-key/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './api-key.repository';
|
||||||
|
export * from './api-key.service';
|
||||||
|
export * from './dto';
|
||||||
|
export * from './response-dto';
|
2
server/libs/domain/src/api-key/response-dto/index.ts
Normal file
2
server/libs/domain/src/api-key/response-dto/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './api-key-create-response.dto';
|
||||||
|
export * from './api-key-response.dto';
|
7
server/libs/domain/src/auth/crypto.repository.ts
Normal file
7
server/libs/domain/src/auth/crypto.repository.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const ICryptoRepository = 'ICryptoRepository';
|
||||||
|
|
||||||
|
export interface ICryptoRepository {
|
||||||
|
randomBytes(size: number): Buffer;
|
||||||
|
hash(data: string | Buffer, saltOrRounds: string | number): Promise<string>;
|
||||||
|
compareSync(data: Buffer | string, encrypted: string): boolean;
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
export * from './crypto.repository';
|
||||||
export * from './dto';
|
export * from './dto';
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
||||||
|
import { APIKeyService } from './api-key';
|
||||||
import { UserService } from './user';
|
import { UserService } from './user';
|
||||||
|
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
//
|
//
|
||||||
|
APIKeyService,
|
||||||
UserService,
|
UserService,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
export * from './api-key';
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './domain.module';
|
export * from './domain.module';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import { IUserRepository } from '@app/domain';
|
||||||
import { UserEntity } from '@app/infra';
|
import { UserEntity } from '@app/infra';
|
||||||
import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
|
||||||
import { AuthUserDto } from '../auth';
|
|
||||||
import { IUserRepository } from '@app/domain';
|
|
||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
import { UserService } from './user.service';
|
import { newUserRepositoryMock } from '../../test';
|
||||||
|
import { AuthUserDto } from '../auth';
|
||||||
import { UpdateUserDto } from './dto/update-user.dto';
|
import { UpdateUserDto } from './dto/update-user.dto';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
const adminUserAuth: AuthUserDto = Object.freeze({
|
const adminUserAuth: AuthUserDto = Object.freeze({
|
||||||
id: 'admin_id',
|
id: 'admin_id',
|
||||||
@ -73,28 +74,18 @@ const adminUserResponse = Object.freeze({
|
|||||||
createdAt: '2021-01-01',
|
createdAt: '2021-01-01',
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe(UserService.name, () => {
|
||||||
let sut: UserService;
|
let sut: UserService;
|
||||||
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
userRepositoryMock = {
|
userRepositoryMock = newUserRepositoryMock();
|
||||||
get: jest.fn(),
|
sut = new UserService(userRepositoryMock);
|
||||||
getAdmin: jest.fn(),
|
|
||||||
getByEmail: jest.fn(),
|
|
||||||
getByOAuthId: jest.fn(),
|
|
||||||
getList: jest.fn(),
|
|
||||||
create: jest.fn(),
|
|
||||||
update: jest.fn(),
|
|
||||||
delete: jest.fn(),
|
|
||||||
restore: jest.fn(),
|
|
||||||
};
|
|
||||||
when(userRepositoryMock.get).calledWith(adminUser.id).mockResolvedValue(adminUser);
|
when(userRepositoryMock.get).calledWith(adminUser.id).mockResolvedValue(adminUser);
|
||||||
when(userRepositoryMock.get).calledWith(adminUser.id, undefined).mockResolvedValue(adminUser);
|
when(userRepositoryMock.get).calledWith(adminUser.id, undefined).mockResolvedValue(adminUser);
|
||||||
when(userRepositoryMock.get).calledWith(immichUser.id).mockResolvedValue(immichUser);
|
when(userRepositoryMock.get).calledWith(immichUser.id).mockResolvedValue(immichUser);
|
||||||
when(userRepositoryMock.get).calledWith(immichUser.id, undefined).mockResolvedValue(immichUser);
|
when(userRepositoryMock.get).calledWith(immichUser.id, undefined).mockResolvedValue(immichUser);
|
||||||
|
|
||||||
sut = new UserService(userRepositoryMock);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAllUsers', () => {
|
describe('getAllUsers', () => {
|
||||||
@ -285,9 +276,7 @@ describe('UserService', () => {
|
|||||||
|
|
||||||
describe('deleteUser', () => {
|
describe('deleteUser', () => {
|
||||||
it('cannot delete admin user', async () => {
|
it('cannot delete admin user', async () => {
|
||||||
const result = sut.deleteUser(adminUserAuth, adminUserAuth.id);
|
await expect(sut.deleteUser(adminUserAuth, adminUserAuth.id)).rejects.toBeInstanceOf(ForbiddenException);
|
||||||
|
|
||||||
await expect(result).rejects.toBeInstanceOf(ForbiddenException);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require the auth user be an admin', async () => {
|
it('should require the auth user be an admin', async () => {
|
||||||
|
12
server/libs/domain/test/api-key.repository.mock.ts
Normal file
12
server/libs/domain/test/api-key.repository.mock.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { IKeyRepository } from '../src';
|
||||||
|
|
||||||
|
export const newKeyRepositoryMock = (): jest.Mocked<IKeyRepository> => {
|
||||||
|
return {
|
||||||
|
create: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
getKey: jest.fn(),
|
||||||
|
getById: jest.fn(),
|
||||||
|
getByUserId: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
9
server/libs/domain/test/crypto.repository.mock.ts
Normal file
9
server/libs/domain/test/crypto.repository.mock.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ICryptoRepository } from '../src';
|
||||||
|
|
||||||
|
export const newCryptoRepositoryMock = (): jest.Mocked<ICryptoRepository> => {
|
||||||
|
return {
|
||||||
|
randomBytes: jest.fn().mockReturnValue(Buffer.from('random-bytes', 'utf8')),
|
||||||
|
compareSync: jest.fn().mockReturnValue(true),
|
||||||
|
hash: jest.fn().mockImplementation((input) => Promise.resolve(`${input} (hashed)`)),
|
||||||
|
};
|
||||||
|
};
|
44
server/libs/domain/test/fixtures.ts
Normal file
44
server/libs/domain/test/fixtures.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { UserEntity } from '@app/infra';
|
||||||
|
import { AuthUserDto } from '../src';
|
||||||
|
|
||||||
|
export const authStub = {
|
||||||
|
admin: Object.freeze<AuthUserDto>({
|
||||||
|
id: 'admin_id',
|
||||||
|
email: 'admin@test.com',
|
||||||
|
isAdmin: true,
|
||||||
|
isPublicUser: false,
|
||||||
|
isAllowUpload: true,
|
||||||
|
}),
|
||||||
|
user1: Object.freeze<AuthUserDto>({
|
||||||
|
id: 'immich_id',
|
||||||
|
email: 'immich@test.com',
|
||||||
|
isAdmin: false,
|
||||||
|
isPublicUser: false,
|
||||||
|
isAllowUpload: true,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const entityStub = {
|
||||||
|
admin: Object.freeze<UserEntity>({
|
||||||
|
...authStub.admin,
|
||||||
|
password: 'admin_password',
|
||||||
|
firstName: 'admin_first_name',
|
||||||
|
lastName: 'admin_last_name',
|
||||||
|
oauthId: '',
|
||||||
|
shouldChangePassword: false,
|
||||||
|
profileImagePath: '',
|
||||||
|
createdAt: '2021-01-01',
|
||||||
|
tags: [],
|
||||||
|
}),
|
||||||
|
user1: Object.freeze<UserEntity>({
|
||||||
|
...authStub.user1,
|
||||||
|
password: 'immich_password',
|
||||||
|
firstName: 'immich_first_name',
|
||||||
|
lastName: 'immich_last_name',
|
||||||
|
oauthId: '',
|
||||||
|
shouldChangePassword: false,
|
||||||
|
profileImagePath: '',
|
||||||
|
createdAt: '2021-01-01',
|
||||||
|
tags: [],
|
||||||
|
}),
|
||||||
|
};
|
4
server/libs/domain/test/index.ts
Normal file
4
server/libs/domain/test/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './api-key.repository.mock';
|
||||||
|
export * from './crypto.repository.mock';
|
||||||
|
export * from './fixtures';
|
||||||
|
export * from './user.repository.mock';
|
15
server/libs/domain/test/user.repository.mock.ts
Normal file
15
server/libs/domain/test/user.repository.mock.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { IUserRepository } from '../src';
|
||||||
|
|
||||||
|
export const newUserRepositoryMock = (): jest.Mocked<IUserRepository> => {
|
||||||
|
return {
|
||||||
|
get: jest.fn(),
|
||||||
|
getAdmin: jest.fn(),
|
||||||
|
getByEmail: jest.fn(),
|
||||||
|
getByOAuthId: jest.fn(),
|
||||||
|
getList: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
restore: jest.fn(),
|
||||||
|
};
|
||||||
|
};
|
9
server/libs/infra/src/auth/crypto.repository.ts
Normal file
9
server/libs/infra/src/auth/crypto.repository.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ICryptoRepository } from '@app/domain';
|
||||||
|
import { compareSync, hash } from 'bcrypt';
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
|
|
||||||
|
export const cryptoRepository: ICryptoRepository = {
|
||||||
|
randomBytes,
|
||||||
|
hash,
|
||||||
|
compareSync,
|
||||||
|
};
|
@ -1,22 +1,8 @@
|
|||||||
import { APIKeyEntity } from '@app/infra';
|
import { IKeyRepository } from '@app/domain';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
import { APIKeyEntity } from '../entities';
|
||||||
export const IKeyRepository = 'IKeyRepository';
|
|
||||||
|
|
||||||
export interface IKeyRepository {
|
|
||||||
create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
|
||||||
update(userId: string, id: number, dto: Partial<APIKeyEntity>): Promise<APIKeyEntity>;
|
|
||||||
delete(userId: string, id: number): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Includes the hashed `key` for verification
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
getKey(id: number): Promise<APIKeyEntity | null>;
|
|
||||||
getById(userId: string, id: number): Promise<APIKeyEntity | null>;
|
|
||||||
getByUserId(userId: string): Promise<APIKeyEntity[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class APIKeyRepository implements IKeyRepository {
|
export class APIKeyRepository implements IKeyRepository {
|
@ -1 +1,2 @@
|
|||||||
|
export * from './api-key.repository';
|
||||||
export * from './user.repository';
|
export * from './user.repository';
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
import { ICryptoRepository, IKeyRepository, IUserRepository } from '@app/domain';
|
||||||
import { databaseConfig, UserEntity } from '@app/infra';
|
import { databaseConfig, UserEntity } from '@app/infra';
|
||||||
import { IUserRepository } from '@app/domain';
|
|
||||||
import { Global, Module, Provider } from '@nestjs/common';
|
import { Global, Module, Provider } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { UserRepository } from './db';
|
import { cryptoRepository } from './auth/crypto.repository';
|
||||||
|
import { APIKeyEntity, UserRepository } from './db';
|
||||||
|
import { APIKeyRepository } from './db/repository';
|
||||||
|
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
//
|
//
|
||||||
|
{ provide: ICryptoRepository, useValue: cryptoRepository },
|
||||||
|
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||||
{ provide: IUserRepository, useClass: UserRepository },
|
{ provide: IUserRepository, useClass: UserRepository },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -14,7 +18,7 @@ const providers: Provider[] = [
|
|||||||
imports: [
|
imports: [
|
||||||
//
|
//
|
||||||
TypeOrmModule.forRoot(databaseConfig),
|
TypeOrmModule.forRoot(databaseConfig),
|
||||||
TypeOrmModule.forFeature([UserEntity]),
|
TypeOrmModule.forFeature([APIKeyEntity, UserEntity]),
|
||||||
],
|
],
|
||||||
providers: [...providers],
|
providers: [...providers],
|
||||||
exports: [...providers],
|
exports: [...providers],
|
||||||
|
@ -145,10 +145,10 @@
|
|||||||
"statements": 20
|
"statements": 20
|
||||||
},
|
},
|
||||||
"./libs/domain/": {
|
"./libs/domain/": {
|
||||||
"branches": 60,
|
"branches": 70,
|
||||||
"functions": 80,
|
"functions": 85,
|
||||||
"lines": 80,
|
"lines": 85,
|
||||||
"statements": 80
|
"statements": 85
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
|
Loading…
Reference in New Issue
Block a user