Commit 498c342b authored by Болатов Ален's avatar Болатов Ален

Merge branch '8' into 'dev'

added user auth

See merge request !10
parents 209092b6 c70ac8fa
This diff is collapsed.
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@typegoose/typegoose": "^11.0.0",
"@types/node": "^18.15.11",
"argon2": "^0.30.3",
"axios": "^1.3.4", "axios": "^1.3.4",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
...@@ -17,9 +20,13 @@ ...@@ -17,9 +20,13 @@
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"mongoose": "^7.0.3", "mongoose": "^7.0.3",
"multer": "^1.4.5-lts.1" "multer": "^1.4.5-lts.1",
"nanoid": "^4.0.2",
"ts-node-dev": "^2.0.0",
"zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.13", "@types/cors": "^2.8.13",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
......
import {Request, Response} from 'express';
import * as userServices from '../services/User';
export const loginOne = async (req: Request, res: Response) => {
try {
const foundUser = await userServices.login(req.body);
res.status(200).send(foundUser);
} catch (error) {
return res.status(500).send(error);
}
};
export const registerOne = async (req: Request, res: Response) => {
try {
await userServices.register(req.body);
res.status(200).send('Inserted successfully');
} catch (error) {
return res.status(500).send(error);
}
};
...@@ -2,6 +2,7 @@ import express, {Express, json, urlencoded} from 'express'; ...@@ -2,6 +2,7 @@ import express, {Express, json, urlencoded} from 'express';
import 'dotenv/config'; import 'dotenv/config';
import cors from 'cors'; import cors from 'cors';
import {mongoose} from './repository/mongoose'; import {mongoose} from './repository/mongoose';
import {UsersRouter} from './routes/user';
mongoose.run(); mongoose.run();
...@@ -11,9 +12,8 @@ app.use(cors()); ...@@ -11,9 +12,8 @@ app.use(cors());
app.use(urlencoded({extended: true})); app.use(urlencoded({extended: true}));
app.use(express.static('public/uploads')); app.use(express.static('public/uploads'));
app.use('/users', UsersRouter);
app.listen(process.env.PORT, () => { app.listen(process.env.PORT, () => {
console.log(`App started on port ${process.env.PORT}`); console.log(`App started on port ${process.env.PORT}`);
}); });
export default interface IUser {
username: string;
password: string;
token: string;
}
import jwt, {Secret, JwtPayload} from 'jsonwebtoken';
import {Request, Response, NextFunction} from 'express';
export const SECRET_KEY: Secret =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsZW4gQm9sYXRvdiIsImlhdCI6MTUxNjIzOTAyMn0.d2_x9z4HZivq8qQUvKEhgROH_zLKwV82bC0a0hXaIvY';
export interface CustomRequest extends Request {
token: string | JwtPayload;
}
export const auth = async (req: Request, res: Response, next: NextFunction) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
throw new Error();
}
const decoded = jwt.verify(token, SECRET_KEY);
(req as CustomRequest).token = decoded;
next();
} catch (err) {
res.status(401).send('Please authenticate');
}
};
import mongoose, {model} from 'mongoose';
import IUser from '../interfaces/IUser';
import bcrypt from 'bcrypt';
const UserSchema: mongoose.Schema<IUser> = new mongoose.Schema<IUser>(
{
username: {
type: String,
required: [true, 'Username is required'],
trim: true,
unique: true,
index: true,
},
password: {
type: String,
required: [true, 'Password is required'],
},
token: {
type: String,
},
},
{versionKey: false}
);
const saltRounds = 8;
UserSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, saltRounds);
}
next();
});
const UserModel = model<IUser>('user', UserSchema);
UserModel.createIndexes();
export default UserModel;
import {Router} from 'express';
import * as userController from '../controllers/user';
const router: Router = Router();
router.post('/register', userController.registerOne);
router.post('/login', userController.loginOne);
export {router as UsersRouter};
import {object, string, TypeOf} from 'zod';
export const createUserSchema = object({
body: object({
firstName: string({
required_error: 'First name is required',
}),
lastName: string({
required_error: 'Last name is required',
}),
password: string({
required_error: 'Password is required',
}).min(6, 'Password is too short - should be min 6 chars'),
passwordConfirmation: string({
required_error: 'Password confirmation is required',
}),
email: string({
required_error: 'Email is required',
}).email('Not a valid email'),
}).refine((data) => data.password === data.passwordConfirmation, {
message: 'Passwords do not match',
path: ['passwordConfirmation'],
}),
});
export const verifyUserSchema = object({
params: object({
id: string(),
verificationCode: string(),
}),
});
export const forgotPasswordSchema = object({
body: object({
email: string({
required_error: 'Email is required',
}).email('Not a valid email'),
}),
});
export const resetPasswordSchema = object({
params: object({
id: string(),
passwordResetCode: string(),
}),
body: object({
password: string({
required_error: 'Password is required',
}).min(6, 'Password is too short - should be min 6 chars'),
passwordConfirmation: string({
required_error: 'Password confirmation is required',
}),
}).refine((data) => data.password === data.passwordConfirmation, {
message: 'Passwords do not match',
path: ['passwordConfirmation'],
}),
});
export type CreateUserInput = TypeOf<typeof createUserSchema>['body'];
export type VerifyUserInput = TypeOf<typeof verifyUserSchema>['params'];
export type ForgotPasswordInput = TypeOf<typeof forgotPasswordSchema>['body'];
export type ResetPasswordInput = TypeOf<typeof resetPasswordSchema>;
import IUser from '../interfaces/IUser';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import {SECRET_KEY} from '../middleware/auth';
import UserModel from '../models/User';
export async function register(user: IUser): Promise<void> {
try {
await UserModel.create(user);
} catch (error) {
throw error;
}
}
export async function login(user: IUser) {
try {
const foundUser = await UserModel.findOne({username: user.username});
if (!foundUser) {
throw new Error('Name of user is not correct');
}
const isMatch = bcrypt.compareSync(user.password, foundUser.password);
if (isMatch) {
const token = jwt.sign(
{_id: foundUser._id?.toString(), username: foundUser.username},
SECRET_KEY,
{
expiresIn: '2 days',
}
);
await UserModel.findOneAndUpdate({username: user.username}, {token});
return {token: token};
} else {
throw new Error('Password is not correct');
}
} catch (error) {
throw error;
}
}
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */ /* Language and Environment */
"target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "target": "ES2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */ // "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
/* Modules */ /* Modules */
"module": "NodeNext" /* Specify what module code is generated. */, "module": "NodeNext" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */ // "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "NodeNext" /* Specify how TypeScript looks up a file from a given module specifier. */, "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ "strictPropertyInitialization": false /* Check for class properties that are declared but not set in the constructor. */,
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
......
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