fix roles

parent 93bc8158
import { Box, Container, CssBaseline } from '@mui/material';
import { AppToolBar } from './components/UI/AppToolbar';
import { Route, Routes, useNavigate } from 'react-router-dom';
import { Products } from './containers/Products';
import { NewProduct } from './containers/NewProduct';
import Auth from './containers/Auth';
import { ProtectedRoute } from './components/UI/ProtectedRoute';
import { useAppDispatch, useAppSelector } from './store/hooks';
import { shallowEqual } from 'react-redux';
import { useEffect } from 'react';
import { notification } from 'antd';
import { useEffect } from 'react';
import { shallowEqual } from 'react-redux';
import { Route, Routes, useNavigate } from 'react-router-dom';
import './App.css';
import { AppToolBar } from './components/UI/AppToolbar';
import { ProtectedRoute } from './components/UI/ProtectedRoute';
import Auth from './containers/Auth';
import { NewProduct } from './containers/NewProduct';
import { Products } from './containers/Products';
import { refreshToken } from './features/usersSlice';
import { useAppDispatch, useAppSelector } from './store/hooks';
function App() {
const dispatch = useAppDispatch();
......@@ -46,7 +46,7 @@ function App() {
<Route
path="/"
element={
<ProtectedRoute user={user}>
<ProtectedRoute isAllowed={!!user}>
<Products />
</ProtectedRoute>
}
......@@ -54,7 +54,7 @@ function App() {
<Route
path="/products"
element={
<ProtectedRoute user={user}>
<ProtectedRoute isAllowed={!!user && user.role === 'admin'}>
<NewProduct />
</ProtectedRoute>
}
......
import { IUser } from '@/containers/Auth';
import React from 'react';
import { Navigate } from 'react-router-dom';
type ProtectedRouteProps = {
user: IUser | null;
isAllowed: boolean;
children: React.ReactNode;
};
export const ProtectedRoute = ({ user, children }: ProtectedRouteProps) => {
if (!user) {
export const ProtectedRoute = ({ isAllowed, children }: ProtectedRouteProps) => {
if (!isAllowed) {
return <Navigate to="/auth" replace />;
}
......
......@@ -15,4 +15,5 @@ export interface IUser {
id: number;
username: string;
accessToken: string;
role: string;
}
import { fetchProducts } from '../features/productsSlice';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import { Button, Grid, Typography } from '@mui/material';
import { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { ProductItem } from '../components/ProductItem';
import { fetchProducts } from '../features/productsSlice';
import { useAppDispatch, useAppSelector } from '../store/hooks';
export const Products = () => {
const dispatch = useAppDispatch();
const { products } = useAppSelector((state) => state.products);
const { user } = useAppSelector((state) => state.user);
useEffect(() => {
dispatch(fetchProducts());
......@@ -34,11 +35,13 @@ export const Products = () => {
))}
</Grid>
<Grid item>
<Button color="primary" component={Link} to={'/products'}>
Add Product
</Button>
</Grid>
{user?.role === 'admin' && (
<Grid item>
<Button color="primary" component={Link} to={'/products'}>
Add Product
</Button>
</Grid>
)}
</Grid>
</>
);
......
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module';
import { CategoryModule } from './category/category.module';
import { Category } from './category/entities/category.entity';
import { RolesGuard } from './common/guards/roles.guard';
import { Product } from './product/entities/product.entity';
import { ProductModule } from './product/product.module';
import { SeedModule } from './seed/seed.module';
......@@ -30,6 +32,11 @@ import { UserModule } from './user/user.module';
SeedModule,
],
controllers: [],
providers: [],
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
......@@ -8,7 +8,7 @@ import {
Request,
UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guarf';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto';
......
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserModule } from 'src/user/user.module';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from 'src/common/strategy/jwt.strategy';
import { UserModule } from 'src/user/user.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
imports: [
......@@ -18,5 +18,6 @@ import { JwtStrategy } from 'src/common/strategy/jwt.strategy';
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
......@@ -18,8 +18,10 @@ export class AuthService {
) {}
async register(registerAuthDto: RegisterDto) {
const { accessToken, refreshToken } =
await this.generateToken(registerAuthDto);
const { accessToken, refreshToken } = await this.generateToken({
role: registerAuthDto.role,
username: registerAuthDto.username,
});
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
registerAuthDto.password = await bcrypt.hash(
......@@ -50,16 +52,22 @@ export class AuthService {
const isMatch = await bcrypt.compare(password, user?.password || '');
if (!isMatch) throw new UnauthorizedException('Неверный логин или пароль');
const { accessToken } = await this.generateToken({ username, password });
const { accessToken } = await this.generateToken({
username,
role: user.role || '',
});
return { accessToken, id: user.id, username: user.username };
}
async signToken(registerAuthDto: RegisterDto, expiresIn: string) {
async signToken(
{ username, role }: { username: string; role: string },
expiresIn: string,
) {
return await this.jwtService.signAsync(
{
username: registerAuthDto.username,
password: registerAuthDto.password,
username,
role,
},
{
secret: 'test',
......@@ -82,9 +90,9 @@ export class AuthService {
return null;
}
async generateToken(registerAuthDto: RegisterDto) {
const accessToken = await this.signToken(registerAuthDto, '30m');
const refreshToken = await this.signToken(registerAuthDto, '7d');
async generateToken({ username, role }: { username: string; role: string }) {
const accessToken = await this.signToken({ username, role }, '30m');
const refreshToken = await this.signToken({ username, role }, '7d');
return {
accessToken,
refreshToken,
......
......@@ -8,4 +8,7 @@ export class RegisterDto {
@IsString()
@IsNotEmpty()
password: string;
@IsString()
role: string;
}
export enum Role {
admin = 'admin',
user = 'user',
}
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { ROLES_KEY } from '../decorators/roles.decarator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private jwtService: JwtService,
) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) return true;
const request = context.switchToHttp().getRequest();
const authHeader = request.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Требуется токен');
}
const token = authHeader.split(' ')[1];
const user = this.jwtService.verify(token);
request.user = user;
if (!requiredRoles.includes(user.role)) {
throw new ForbiddenException('Недостаточно прав');
}
return true;
}
}
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
......@@ -12,7 +12,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}
validate(payload: { username: string }) {
return { username: payload.username };
validate(payload: { username: string; role: string }) {
return { username: payload.username, role: payload.role };
}
}
import {
Body,
Controller,
Delete,
Get,
Post,
Body,
Param,
Delete,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
UploadedFile,
} from '@nestjs/common';
import { ProductService } from './product.service';
import { CreateProductDto } from './dto/create-product.dto';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guarf';
import { FileInterceptor } from '@nestjs/platform-express';
import { Roles } from 'src/common/decorators/roles.decarator';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';
import { CreateProductDto } from './dto/create-product.dto';
import { ProductService } from './product.service';
@Controller('product')
export class ProductController {
......@@ -28,6 +29,7 @@ export class ProductController {
}
@UseGuards(JwtAuthGuard)
@Roles('user')
@Get()
findAll() {
return this.productService.findAll();
......
import { Module } from '@nestjs/common';
import { ProductService } from './product.service';
import { ProductController } from './product.controller';
import { Product } from './entities/product.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Category } from 'src/category/entities/category.entity';
import { MulterModule } from '@nestjs/platform-express';
import { TypeOrmModule } from '@nestjs/typeorm';
import { diskStorage } from 'multer';
import { extname, join } from 'path';
import { Category } from 'src/category/entities/category.entity';
import { Product } from './entities/product.entity';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
@Module({
imports: [
......
import { Module } from '@nestjs/common';
import { SeedService } from './seed.service';
import { SeedController } from './seed.controller';
import { ProductModule } from 'src/product/product.module';
import { AuthModule } from 'src/auth/auth.module';
import { CategoryModule } from 'src/category/category.module';
import { ProductModule } from 'src/product/product.module';
import { SeedController } from './seed.controller';
import { SeedService } from './seed.service';
@Module({
imports: [ProductModule, CategoryModule],
imports: [ProductModule, CategoryModule, AuthModule],
controllers: [SeedController],
providers: [SeedService],
})
......
import { faker } from '@faker-js/faker';
import { Injectable } from '@nestjs/common';
import { AuthService } from 'src/auth/auth.service';
import { CategoryService } from 'src/category/category.service';
import { ProductService } from 'src/product/product.service';
import { faker } from '@faker-js/faker';
import { Category } from 'src/category/entities/category.entity';
import { Role } from 'src/common/constants/roles';
import { Product } from 'src/product/entities/product.entity';
import { ProductService } from 'src/product/product.service';
@Injectable()
export class SeedService {
constructor(
private productService: ProductService,
private categoryService: CategoryService,
private authService: AuthService,
) {}
async create() {
......@@ -19,6 +22,18 @@ export class SeedService {
const categories: Category[] = [];
const products: Product[] = [];
await this.authService.register({
username: 'admin',
role: Role.admin,
password: '123456',
});
await this.authService.register({
username: 'user',
role: Role.user,
password: '123456',
});
for (let i = 0; i < 50; i++) {
const name = faker.food.dish();
const description = faker.food.description();
......
......@@ -11,4 +11,7 @@ export class CreateUserDto {
@IsString()
refreshToken: string;
@IsString()
role: string;
}
import { Role } from 'src/common/constants/roles';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
......@@ -13,4 +14,7 @@ export class User {
@Column({ nullable: true })
refreshToken?: string;
@Column({ nullable: true, enum: Role })
role: string;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment