fix roles

parent 93bc8158
import { Box, Container, CssBaseline } from '@mui/material'; 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 { notification } from 'antd';
import { useEffect } from 'react';
import { shallowEqual } from 'react-redux';
import { Route, Routes, useNavigate } from 'react-router-dom';
import './App.css'; 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 { refreshToken } from './features/usersSlice';
import { useAppDispatch, useAppSelector } from './store/hooks';
function App() { function App() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
...@@ -46,7 +46,7 @@ function App() { ...@@ -46,7 +46,7 @@ function App() {
<Route <Route
path="/" path="/"
element={ element={
<ProtectedRoute user={user}> <ProtectedRoute isAllowed={!!user}>
<Products /> <Products />
</ProtectedRoute> </ProtectedRoute>
} }
...@@ -54,7 +54,7 @@ function App() { ...@@ -54,7 +54,7 @@ function App() {
<Route <Route
path="/products" path="/products"
element={ element={
<ProtectedRoute user={user}> <ProtectedRoute isAllowed={!!user && user.role === 'admin'}>
<NewProduct /> <NewProduct />
</ProtectedRoute> </ProtectedRoute>
} }
......
import { IUser } from '@/containers/Auth';
import React from 'react'; import React from 'react';
import { Navigate } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
type ProtectedRouteProps = { type ProtectedRouteProps = {
user: IUser | null; isAllowed: boolean;
children: React.ReactNode; children: React.ReactNode;
}; };
export const ProtectedRoute = ({ user, children }: ProtectedRouteProps) => { export const ProtectedRoute = ({ isAllowed, children }: ProtectedRouteProps) => {
if (!user) { if (!isAllowed) {
return <Navigate to="/auth" replace />; return <Navigate to="/auth" replace />;
} }
......
...@@ -15,4 +15,5 @@ export interface IUser { ...@@ -15,4 +15,5 @@ export interface IUser {
id: number; id: number;
username: string; username: string;
accessToken: string; accessToken: string;
role: string;
} }
import { fetchProducts } from '../features/productsSlice';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import { Button, Grid, Typography } from '@mui/material'; import { Button, Grid, Typography } from '@mui/material';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ProductItem } from '../components/ProductItem'; import { ProductItem } from '../components/ProductItem';
import { fetchProducts } from '../features/productsSlice';
import { useAppDispatch, useAppSelector } from '../store/hooks';
export const Products = () => { export const Products = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { products } = useAppSelector((state) => state.products); const { products } = useAppSelector((state) => state.products);
const { user } = useAppSelector((state) => state.user);
useEffect(() => { useEffect(() => {
dispatch(fetchProducts()); dispatch(fetchProducts());
...@@ -34,11 +35,13 @@ export const Products = () => { ...@@ -34,11 +35,13 @@ export const Products = () => {
))} ))}
</Grid> </Grid>
<Grid item> {user?.role === 'admin' && (
<Button color="primary" component={Link} to={'/products'}> <Grid item>
Add Product <Button color="primary" component={Link} to={'/products'}>
</Button> Add Product
</Grid> </Button>
</Grid>
)}
</Grid> </Grid>
</> </>
); );
......
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { CategoryModule } from './category/category.module'; import { CategoryModule } from './category/category.module';
import { Category } from './category/entities/category.entity'; import { Category } from './category/entities/category.entity';
import { RolesGuard } from './common/guards/roles.guard';
import { Product } from './product/entities/product.entity'; import { Product } from './product/entities/product.entity';
import { ProductModule } from './product/product.module'; import { ProductModule } from './product/product.module';
import { SeedModule } from './seed/seed.module'; import { SeedModule } from './seed/seed.module';
...@@ -30,6 +32,11 @@ import { UserModule } from './user/user.module'; ...@@ -30,6 +32,11 @@ import { UserModule } from './user/user.module';
SeedModule, SeedModule,
], ],
controllers: [], controllers: [],
providers: [], providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
}) })
export class AppModule {} export class AppModule {}
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
Request, Request,
UseGuards, UseGuards,
} from '@nestjs/common'; } 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 { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto'; import { RegisterDto } from './dto/register.dto';
......
import { Module } from '@nestjs/common'; 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 { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from 'src/common/strategy/jwt.strategy'; 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({ @Module({
imports: [ imports: [
...@@ -18,5 +18,6 @@ import { JwtStrategy } from 'src/common/strategy/jwt.strategy'; ...@@ -18,5 +18,6 @@ import { JwtStrategy } from 'src/common/strategy/jwt.strategy';
], ],
controllers: [AuthController], controllers: [AuthController],
providers: [AuthService, JwtStrategy], providers: [AuthService, JwtStrategy],
exports: [AuthService],
}) })
export class AuthModule {} export class AuthModule {}
...@@ -18,8 +18,10 @@ export class AuthService { ...@@ -18,8 +18,10 @@ export class AuthService {
) {} ) {}
async register(registerAuthDto: RegisterDto) { async register(registerAuthDto: RegisterDto) {
const { accessToken, refreshToken } = const { accessToken, refreshToken } = await this.generateToken({
await this.generateToken(registerAuthDto); role: registerAuthDto.role,
username: registerAuthDto.username,
});
const salt = await bcrypt.genSalt(SALT_WORK_FACTOR); const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
registerAuthDto.password = await bcrypt.hash( registerAuthDto.password = await bcrypt.hash(
...@@ -50,16 +52,22 @@ export class AuthService { ...@@ -50,16 +52,22 @@ export class AuthService {
const isMatch = await bcrypt.compare(password, user?.password || ''); const isMatch = await bcrypt.compare(password, user?.password || '');
if (!isMatch) throw new UnauthorizedException('Неверный логин или пароль'); 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 }; 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( return await this.jwtService.signAsync(
{ {
username: registerAuthDto.username, username,
password: registerAuthDto.password, role,
}, },
{ {
secret: 'test', secret: 'test',
...@@ -82,9 +90,9 @@ export class AuthService { ...@@ -82,9 +90,9 @@ export class AuthService {
return null; return null;
} }
async generateToken(registerAuthDto: RegisterDto) { async generateToken({ username, role }: { username: string; role: string }) {
const accessToken = await this.signToken(registerAuthDto, '30m'); const accessToken = await this.signToken({ username, role }, '30m');
const refreshToken = await this.signToken(registerAuthDto, '7d'); const refreshToken = await this.signToken({ username, role }, '7d');
return { return {
accessToken, accessToken,
refreshToken, refreshToken,
......
...@@ -8,4 +8,7 @@ export class RegisterDto { ...@@ -8,4 +8,7 @@ export class RegisterDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
password: string; 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 { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { export class JwtStrategy extends PassportStrategy(Strategy) {
...@@ -12,7 +12,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { ...@@ -12,7 +12,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
}); });
} }
validate(payload: { username: string }) { validate(payload: { username: string; role: string }) {
return { username: payload.username }; return { username: payload.username, role: payload.role };
} }
} }
import { import {
Body,
Controller, Controller,
Delete,
Get, Get,
Post,
Body,
Param, Param,
Delete, Post,
UploadedFile,
UseGuards, UseGuards,
UseInterceptors, UseInterceptors,
UploadedFile,
} from '@nestjs/common'; } 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 { 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') @Controller('product')
export class ProductController { export class ProductController {
...@@ -28,6 +29,7 @@ export class ProductController { ...@@ -28,6 +29,7 @@ export class ProductController {
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Roles('user')
@Get() @Get()
findAll() { findAll() {
return this.productService.findAll(); return this.productService.findAll();
......
import { Module } from '@nestjs/common'; 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 { MulterModule } from '@nestjs/platform-express';
import { TypeOrmModule } from '@nestjs/typeorm';
import { diskStorage } from 'multer'; import { diskStorage } from 'multer';
import { extname, join } from 'path'; 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({ @Module({
imports: [ imports: [
......
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { SeedService } from './seed.service'; import { AuthModule } from 'src/auth/auth.module';
import { SeedController } from './seed.controller';
import { ProductModule } from 'src/product/product.module';
import { CategoryModule } from 'src/category/category.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({ @Module({
imports: [ProductModule, CategoryModule], imports: [ProductModule, CategoryModule, AuthModule],
controllers: [SeedController], controllers: [SeedController],
providers: [SeedService], providers: [SeedService],
}) })
......
import { faker } from '@faker-js/faker';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { AuthService } from 'src/auth/auth.service';
import { CategoryService } from 'src/category/category.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 { Category } from 'src/category/entities/category.entity';
import { Role } from 'src/common/constants/roles';
import { Product } from 'src/product/entities/product.entity'; import { Product } from 'src/product/entities/product.entity';
import { ProductService } from 'src/product/product.service';
@Injectable() @Injectable()
export class SeedService { export class SeedService {
constructor( constructor(
private productService: ProductService, private productService: ProductService,
private categoryService: CategoryService, private categoryService: CategoryService,
private authService: AuthService,
) {} ) {}
async create() { async create() {
...@@ -19,6 +22,18 @@ export class SeedService { ...@@ -19,6 +22,18 @@ export class SeedService {
const categories: Category[] = []; const categories: Category[] = [];
const products: Product[] = []; 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++) { for (let i = 0; i < 50; i++) {
const name = faker.food.dish(); const name = faker.food.dish();
const description = faker.food.description(); const description = faker.food.description();
......
...@@ -11,4 +11,7 @@ export class CreateUserDto { ...@@ -11,4 +11,7 @@ export class CreateUserDto {
@IsString() @IsString()
refreshToken: string; refreshToken: string;
@IsString()
role: string;
} }
import { Role } from 'src/common/constants/roles';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity() @Entity()
...@@ -13,4 +14,7 @@ export class User { ...@@ -13,4 +14,7 @@ export class User {
@Column({ nullable: true }) @Column({ nullable: true })
refreshToken?: string; 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