diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 989ae850..e0d092bf 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -14,10 +14,8 @@ services: proxy: image: nginx:1.17.3-alpine - ports: - 8090:80 - volumes: - ./nginx-config/local.conf:/etc/nginx/conf.d/default.conf - ./public/build:/htdocs diff --git a/docker-compose.yml b/docker-compose.yml index dac620f9..81c3fcda 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,8 +8,21 @@ services: POSTGRES_DB: bc POSTGRES_USER: bc POSTGRES_PASSWORD: bc + healthcheck: + interval: 10s + retries: 10 + test: + [ + 'CMD', + 'pg_isready', + '-U', + '${POSTGRES_USER}', + '-P', + '${POSTGRES_PASSWORD}', + ] + timeout: 45s volumes: - - ./pg.sql:/docker-entrypoint-initdb.d/pg.sql + - ./pg.sql:/docker-entrypoint-initdb.d/pg.sql nodejs: image: neuralegion/brokencrystals @@ -21,8 +34,8 @@ services: cpus: 3.5 logging: options: - max-file: "5" - max-size: "10m" + max-file: '5' + max-size: '10m' depends_on: - db - keycloak @@ -31,10 +44,10 @@ services: image: neuralegion/brokencrystals-client restart: always environment: - CERTBOT_EMAIL: "anatol@neuralegion.com" + CERTBOT_EMAIL: 'anatol@neuralegion.com' ports: - - "80:80" - - "443:443" + - '80:80' + - '443:443' depends_on: - nodejs volumes: @@ -46,28 +59,41 @@ services: restart: always logging: options: - max-file: "5" - max-size: "10m" + max-file: '5' + max-size: '10m' volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 300 --debug keycloak-db: - image: "postgres:12.2-alpine" + image: 'postgres:12.2-alpine' environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: password + healthcheck: + interval: 10s + retries: 10 + test: + [ + 'CMD', + 'pg_isready', + '-U', + '${POSTGRES_USER}', + '-P', + '${POSTGRES_PASSWORD}', + ] + timeout: 45s restart: on-failure stdin_open: true tty: true volumes: - - "./keycloak/db:/var/lib/postgresql/data/" + - './keycloak/db:/var/lib/postgresql/data/' keycloak: image: jboss/keycloak:latest volumes: - - "./keycloak/imports/realm-export.json:/opt/jboss/keycloak/imports/realm-export.json" + - './keycloak/imports/realm-export.json:/opt/jboss/keycloak/imports/realm-export.json' environment: DB_VENDOR: POSTGRES DB_ADDR: keycloak-db @@ -79,7 +105,13 @@ services: KEYCLOAK_PASSWORD: Pa55w0rd KEYCLOAK_IMPORT: /opt/jboss/keycloak/imports/realm-export.json -Dkeycloak.profile.feature.upload_scripts=enabled healthcheck: - test: [ "CMD", "curl", "-f", "http://localhost:8080/auth/realms/brokencrystals/health/check/database" ] + test: + [ + 'CMD', + 'curl', + '-f', + 'http://localhost:8080/auth/realms/brokencrystals/health/check/database', + ] timeout: 10s interval: 30s retries: 3 diff --git a/pg.sql b/pg.sql index adc31790..e3966d8d 100644 --- a/pg.sql +++ b/pg.sql @@ -9,7 +9,7 @@ create table "product" ("id" serial primary key, "created_at" timestamptz(0) not set session_replication_role = 'origin'; --password is admin -INSERT INTO public."user" (created_at, updated_at, email, password, first_name, last_name, is_admin, photo) VALUES (now(), now(), 'admin', '$2b$10$BBJjmVNNdyEgv7pV/zQR9u/ssIuwZsdDJbowW/Dgp28uws3GmO0Ky', 'admin', 'admin', false, null); +INSERT INTO "user" (created_at, updated_at, email, password, first_name, last_name, is_admin, photo) VALUES (now(), now(), 'admin', '$2b$10$BBJjmVNNdyEgv7pV/zQR9u/ssIuwZsdDJbowW/Dgp28uws3GmO0Ky', 'admin', 'admin', false, null); --insert default products into the table INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES ('Healing', '/api/file?path=config/products/crystals/amethyst.jpg&type=image/jpg', 'Amethyst', 'a violet variety of quartz'); @@ -19,4 +19,4 @@ INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES (' INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES ('Healing', '/api/file?path=config/products/crystals/amber.jpg&type=image/jpg', 'Amber', 'fossilized tree resin'); INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES ('Jewellery', '/api/file?path=config/products/crystals/emerald.jpg&type=image/jpg', 'Emerald', 'symbol of fertility and life'); INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES ('Jewellery', '/api/file?path=config/products/crystals/shattuckite.jpg&type=image/jpg', 'Shattuckite', 'mistery'); -INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES ('Gemstones', '/api/file?path=config/products/crystals/bismuth.jpg&type=image/jpg', 'Bismuth', 'rainbow'); \ No newline at end of file +INSERT INTO "product" ("category", "photo_url", "name", "description") VALUES ('Gemstones', '/api/file?path=config/products/crystals/bismuth.jpg&type=image/jpg', 'Bismuth', 'rainbow'); diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index e3e9717b..d08bb522 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,7 +1,6 @@ import { BadRequestException, Body, - Query, Controller, Get, HttpStatus, @@ -13,7 +12,7 @@ import { UnauthorizedException, UseGuards, } from '@nestjs/common'; -import { createHash } from 'crypto'; +import { createHash, randomBytes } from 'crypto'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { User } from '../model/user.entity'; import { LdapQueryHandler } from '../users/ldap.query.handler'; @@ -42,7 +41,6 @@ import { AuthService, JwtProcessorType } from './auth.service'; import { passwordMatches } from './credentials.utils'; import { JwtType } from './jwt/jwt.type.decorator'; import { FastifyReply, FastifyRequest } from 'fastify'; -import { randomBytes } from 'crypto'; import { CsrfGuard } from './csrf.guard'; import { ClientType, KeyCloakService } from '../keycloak/keycloak.service'; @@ -64,70 +62,6 @@ export class AuthController { private readonly authService: AuthService, ) {} - private async loginOidc(req: LoginRequest): Promise { - try { - const { - token_type, - access_token, - } = await this.keyCloakService.generateToken({ - username: req.user, - password: req.password, - }); - - return { - email: req.user, - ldapProfileLink: LdapQueryHandler.LDAP_SEARCH_QUERY(req.user), - token: `${token_type} ${access_token}`, - }; - } catch (err) { - if (err.response.status === 401) { - throw new UnauthorizedException({ - error: 'Invalid credentials', - location: __filename, - }); - } - - throw new InternalServerErrorException({ - error: err.message, - location: __filename, - }); - } - } - - private async login(req: LoginRequest): Promise { - let user: User; - - try { - user = await this.usersService.findByEmail(req.user); - } catch (err) { - throw new InternalServerErrorException({ - error: err.message, - location: __filename, - }); - } - - if (!user || !(await passwordMatches(req.password, user.password))) { - throw new UnauthorizedException({ - error: 'Invalid credentials', - location: __filename, - }); - } - - const token = await this.authService.createToken( - { - user: user.email, - exp: 90 + Math.floor(Date.now() / 1000), - }, - JwtProcessorType.RSA, - ); - - return { - token, - email: user.email, - ldapProfileLink: LdapQueryHandler.LDAP_SEARCH_QUERY(user.email), - }; - } - @Post('/admin/login') @ApiResponse({ type: LoginResponse, @@ -530,4 +464,68 @@ export class AuthController { secret: 'this is our secret', }; } + + private async loginOidc(req: LoginRequest): Promise { + try { + const { + token_type, + access_token, + } = await this.keyCloakService.generateToken({ + username: req.user, + password: req.password, + }); + + return { + email: req.user, + ldapProfileLink: LdapQueryHandler.LDAP_SEARCH_QUERY(req.user), + token: `${token_type} ${access_token}`, + }; + } catch (err) { + if (err.response.status === 401) { + throw new UnauthorizedException({ + error: 'Invalid credentials', + location: __filename, + }); + } + + throw new InternalServerErrorException({ + error: err.message, + location: __filename, + }); + } + } + + private async login(req: LoginRequest): Promise { + let user: User; + + try { + user = await this.usersService.findByEmail(req.user); + } catch (err) { + throw new InternalServerErrorException({ + error: err.message, + location: __filename, + }); + } + + if (!user || !(await passwordMatches(req.password, user.password))) { + throw new UnauthorizedException({ + error: 'Invalid credentials', + location: __filename, + }); + } + + const token = await this.authService.createToken( + { + user: user.email, + exp: 90 + Math.floor(Date.now() / 1000), + }, + JwtProcessorType.RSA, + ); + + return { + token, + email: user.email, + ldapProfileLink: LdapQueryHandler.LDAP_SEARCH_QUERY(user.email), + }; + } } diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index 75f0fa29..a4fc8fc7 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -1,17 +1,8 @@ -import { - Body, - Controller, - Get, - Logger, - Post, - Query, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, Logger, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthGuard } from '../auth/auth.guard'; import { JwtProcessorType } from '../auth/auth.service'; import { JwtType } from '../auth/jwt/jwt.type.decorator'; -import { CreateProductRequest } from './api/CreateProductRequest'; import { ProductDto } from './api/ProductDto'; import { ProductsService } from './products.service'; import { Product } from '../model/product.entity'; @@ -37,7 +28,7 @@ export class ProductsController { async getProducts(): Promise { this.logger.debug('Get all products.'); const allProducts = await this.productsService.findAll(); - return allProducts.map((p: Product) => new ProductDto(p)); + return allProducts.map((p: Product) => new ProductDto(p)); } @Get('latest') diff --git a/src/products/products.service.ts b/src/products/products.service.ts index 8dbcedf6..6c17a291 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -1,6 +1,6 @@ -import { EntityManager, EntityRepository, MikroORM } from '@mikro-orm/core'; +import { EntityRepository } from '@mikro-orm/core'; import { InjectRepository } from '@mikro-orm/nestjs'; -import { Inject, Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { Product } from '../model/product.entity'; @Injectable() @@ -10,7 +10,6 @@ export class ProductsService { constructor( @InjectRepository(Product) private readonly productsRepository: EntityRepository, - private readonly em: EntityManager, ) {} async findAll(): Promise { diff --git a/src/testimonials/testimonials.service.ts b/src/testimonials/testimonials.service.ts index 9f7f12df..35f3d0f2 100644 --- a/src/testimonials/testimonials.service.ts +++ b/src/testimonials/testimonials.service.ts @@ -1,6 +1,6 @@ -import { EntityManager, EntityRepository, MikroORM } from '@mikro-orm/core'; +import { EntityManager, EntityRepository } from '@mikro-orm/core'; import { InjectRepository } from '@mikro-orm/nestjs'; -import { Inject, Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { Testimonial } from '../model/testimonial.entity'; @Injectable()