added new code
This commit is contained in:
parent
f8baeba66d
commit
ff1f6bf560
25
.env
Normal file
25
.env
Normal file
@ -0,0 +1,25 @@
|
||||
ENV=dev
|
||||
HOST=localhost
|
||||
PORT=3000
|
||||
|
||||
# DATABASE
|
||||
POSTGRES_URI=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=authentication
|
||||
POSTGRES_DB_TEST=authentication_test
|
||||
POSTGRES_USER=admin
|
||||
POSTGRES_PASSWORD=admin
|
||||
|
||||
# BCRYPT
|
||||
SECRET_PEPPER=github@adhamhaddad
|
||||
SALT_ROUNDS=10
|
||||
|
||||
# JWT
|
||||
JWT_SECRET_ACCESS_TOKEN=
|
||||
JWT_SECRET_REFRESH_TOKEN=
|
||||
JWT_ACCESS_TOKEN_EXPIRATION=5m
|
||||
JWT_REFRESH_TOKEN_EXPIRATION=1y
|
||||
|
||||
# URL
|
||||
BACKEND_HOST=http://127.0.0.1:8000
|
||||
FRONTEND_HOST=http://127.0.0.1:3000
|
||||
25
.eslintrc.js
Normal file
25
.eslintrc.js
Normal file
@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 13,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'prettier'],
|
||||
rules: {
|
||||
'prettier/prettier': 1,
|
||||
quotes: ['error', 'single'],
|
||||
'no-console': 0,
|
||||
'no-var': 'error',
|
||||
'prefer-const': 'error',
|
||||
},
|
||||
};
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -72,12 +72,6 @@ web_modules/
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
20
.prettierrc.json
Normal file
20
.prettierrc.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"singleAttributePerLine": false,
|
||||
"bracketSameLine": false,
|
||||
"jsxSingleQuote": true,
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"trailingComma" : "none"
|
||||
}
|
||||
35
README.md
35
README.md
@ -1,2 +1,37 @@
|
||||
# Nodejs-Refresh-Token
|
||||
|
||||
## Description
|
||||
|
||||
Full Authentication & Authorization using nodejs, bcrypt, jsonwebtoken, pg thats integrate accessToken, refreshToken with advanced way
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Node v14.15.1 (LTS) or more recent. While older versions can work it is advisable to keep node to latest LTS version
|
||||
|
||||
- npm 6.14.8 (LTS) or more recent, Yarn can work but was not tested for this project
|
||||
|
||||
## Installation
|
||||
|
||||
### Database setup
|
||||
|
||||
1. Open postgres terminal with: `psql postgres`
|
||||
|
||||
1- `CREATE DATABASE authentication;`
|
||||
|
||||
2- `CREATE ROLE admin WITH PASSWORD 'admin';`
|
||||
|
||||
3- `ALTER ROLE admin WITH SUPERUSER CREATEROLE CREATEDB LOGIN;`
|
||||
|
||||
4- `GRANT ALL PRIVILEGES ON DATABASE authentication TO admin;`
|
||||
|
||||
2. Second to install the node_modules run `npm install` or `yarn`. After installation is done start the api in dev mode with `npm run dev` or `yarn dev`.
|
||||
|
||||
## Unit Tests
|
||||
|
||||
No Unit test available now.
|
||||
|
||||
## Built With
|
||||
|
||||
- [Node](https://nodejs.org) - Javascript Runtime
|
||||
- [Express](https://expressjs.com/) - Javascript API Framework
|
||||
- [PostgreSQL](https://www.postgresql.org/) - Open Source Relational Database
|
||||
|
||||
18
database.json
Normal file
18
database.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"dev": {
|
||||
"driver": "pg",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "authentication",
|
||||
"user": "admin",
|
||||
"password": "admin"
|
||||
},
|
||||
"test": {
|
||||
"driver": "pg",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "authentication_test",
|
||||
"user": "admin",
|
||||
"password": "admin"
|
||||
}
|
||||
}
|
||||
51
keys/accessToken/private.key
Normal file
51
keys/accessToken/private.key
Normal file
@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
14
keys/accessToken/public.key
Normal file
14
keys/accessToken/public.key
Normal file
@ -0,0 +1,14 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0RBJ7/i1GKYguWdey1Pz
|
||||
an/gFtbUIa7z/TJWphZFEXpj1BeAoNVLQgJVsPZ/GHqvPWal5vhdAbZEAK/Yf7FG
|
||||
wpcYzEXNmecrFDjYEhb5bs6GfpEvdjZxkZ+eM82XYDRmK9WgQmuqUiwcMlOkbcVw
|
||||
2VHflnBq0x0zYXTBO4CVMyLWvvmi25+c25AOSRq+QhhFQFHz2xD1gnBv1sgo7SVk
|
||||
P9RpaH454T4NTZ6C9loff/N8LN+AAous9FAZ/3PC9hicr5jkXI7w9x0wzfZkcEpC
|
||||
jFk1deyZwcJ0U86ujd2J5uDuBzhcPBO45mvfDs0VZ39wpgxQlmSUqFpFOrejjU1L
|
||||
Oxjdd6iCkA9NSow98A/F/AbSKHbVmjr1WIv1lrgjO6OldQiHv19GoDxTxTGhVLqQ
|
||||
Sn0YXmOk1Zk8O0kuZt5cfkF1kuxQUBV/AKCKkNsWH6grBCdqIB7xNoUXK5EOiSMH
|
||||
wx1vA9LhIi1cCOxUBhgCpXMla8VjEbOnU8NIRGoATdodKNaDTxib1N7XpkofSlpK
|
||||
Px7YfGK5t/z1ODDpQ1vvHIb7cXJW+rnFvOZXt62m5HDdGyQW9Qd9yhwd6qL0eurd
|
||||
4JaU78OfG2ussEi7M3qV12yiEW7V1Gpr7XjOf/GBx3GhW7oHqKOZKCanZv5xhliv
|
||||
5rkLSnnmcLHxm/m0pGTr96kCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
||||
51
keys/refreshToken/private.key
Normal file
51
keys/refreshToken/private.key
Normal file
@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
14
keys/refreshToken/public.key
Normal file
14
keys/refreshToken/public.key
Normal file
@ -0,0 +1,14 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsWgVuFLjnxUgUbXn7ayy
|
||||
PfeAatQ9q8Pn9ogA9gs+L7RwixKo3ijFuKl/GireTZ2xMXVPPposLF/g9B4Gkq+G
|
||||
RluL4eNRV49W+lQQl57fUfuUBlsmCEAlXRbhNnjXCeKc/bwH3J1uIrBGqqTdqAmd
|
||||
QUZCOHreUp1k9LzU00CYezA6OkaqfzQbNUb7IqO+xI/frJf5yr9r2NlggXvc+phY
|
||||
U4qQefHzSoIPZgq13M4KYMs3N5LjaHi1BgmBDFr39nzPsZIYe6vvVPoVfpmuVIa0
|
||||
/ICfjuqDPoG8UbQrdJXOEy/CkF82kTedIdG+XsKL+xCjdq04k0lJOs3ePFqFdOsJ
|
||||
C/ZIzYVPGyS+wfanxsyQqnblf/75NR+XNcpOm58OXT9WyDRd4E4OTRxyDKsF3XGy
|
||||
m49/sd0n3pFOmWshOqPySowIUUgLIj5N2+CNCM+QOlnE5mMnQQEWNFENRGcgDl4U
|
||||
DUm19gEzmqP9Relo//u5FLpC11T0lnbLHsVXRQD602onDX6YMY7vijyPNau1bFBj
|
||||
8Dvlkbn2V46aagVW9Io24DJiaT0RCyYr9GsxTLiiyTg/w94CZZYfI8MsveXK9RNW
|
||||
KxrGyCOaiDdQC1q5aAyS44Xx6dWG/Oow2p528Rq6DMugQ1GTZ7guOVWosjGHQS/5
|
||||
S8HRlCMuN4xNjabYilEu+wMCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
||||
53
migrations/20230424202313-users-table.js
Normal file
53
migrations/20230424202313-users-table.js
Normal file
@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
var dbm;
|
||||
var type;
|
||||
var seed;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Promise;
|
||||
|
||||
/**
|
||||
* We receive the dbmigrate dependency from dbmigrate initially.
|
||||
* This enables us to not have to rely on NODE_PATH.
|
||||
*/
|
||||
exports.setup = function(options, seedLink) {
|
||||
dbm = options.dbmigrate;
|
||||
type = dbm.dataType;
|
||||
seed = seedLink;
|
||||
Promise = options.Promise;
|
||||
};
|
||||
|
||||
exports.up = function(db) {
|
||||
var filePath = path.join(__dirname, 'sqls', '20230424202313-users-table-up.sql');
|
||||
return new Promise( function( resolve, reject ) {
|
||||
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
|
||||
if (err) return reject(err);
|
||||
console.log('received data: ' + data);
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
})
|
||||
.then(function(data) {
|
||||
return db.runSql(data);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(db) {
|
||||
var filePath = path.join(__dirname, 'sqls', '20230424202313-users-table-down.sql');
|
||||
return new Promise( function( resolve, reject ) {
|
||||
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
|
||||
if (err) return reject(err);
|
||||
console.log('received data: ' + data);
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
})
|
||||
.then(function(data) {
|
||||
return db.runSql(data);
|
||||
});
|
||||
};
|
||||
|
||||
exports._meta = {
|
||||
"version": 1
|
||||
};
|
||||
1
migrations/sqls/20230424202313-users-table-down.sql
Normal file
1
migrations/sqls/20230424202313-users-table-down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS users;
|
||||
11
migrations/sqls/20230424202313-users-table-up.sql
Normal file
11
migrations/sqls/20230424202313-users-table-up.sql
Normal file
@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATE,
|
||||
deleted_at DATE
|
||||
);
|
||||
53
package.json
Normal file
53
package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-validator": "^6.14.2",
|
||||
"helmet": "^6.1.5",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"pg": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/eslint-config-prettier": "^6.11.0",
|
||||
"@types/eslint-plugin-prettier": "^3.1.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-validator": "^3.0.0",
|
||||
"@types/helmet": "^4.0.0",
|
||||
"@types/jasmine": "^4.3.1",
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/morgan": "^1.9.4",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/nodemon": "^1.19.2",
|
||||
"@types/pg": "^8.6.6",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/typescript": "^2.0.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"jasmine": "^4.6.0",
|
||||
"jasmine-spec-reporter": "^7.0.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"prettier": "^2.8.8",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"prettier": "prettier --write src/**/*.ts && prettier --write src/*.ts",
|
||||
"lint": "eslint --fix --ext .ts .",
|
||||
"format": "npm run prettier && npm run lint",
|
||||
"build": "tsc",
|
||||
"jasmine": "jasmine",
|
||||
"migrate:up": "db-migrate up",
|
||||
"migrate:down": "db-migrate up",
|
||||
"migrate:reset": "db-migrate reset",
|
||||
"test": "export ENV=test && tsc && db-migrate up --env test && jasmine && db-migrate reset --env test",
|
||||
"start": "tsc && node dist/server.ts",
|
||||
"dev": "nodemon src/server.ts"
|
||||
}
|
||||
}
|
||||
7
spec/support/jasmine.json
Normal file
7
spec/support/jasmine.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"spec_dir": "dist",
|
||||
"spec_files": ["**/**/*[sS]pec.js"],
|
||||
"helpers": ["helpers/**/*.js"],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
||||
52
sql/schema.sql
Normal file
52
sql/schema.sql
Normal file
@ -0,0 +1,52 @@
|
||||
SET client_min_messages = warning;
|
||||
-- -------------------------
|
||||
-- Database authentication
|
||||
-- -------------------------
|
||||
DROP DATABASE IF EXISTS authentication;
|
||||
--
|
||||
--
|
||||
CREATE DATABASE authentication;
|
||||
-- -------------------------
|
||||
-- Database authentication_test
|
||||
-- -------------------------
|
||||
DROP DATABASE IF EXISTS authentication_test;
|
||||
--
|
||||
--
|
||||
CREATE DATABASE authentication_test;
|
||||
-- -------------------------
|
||||
-- Role admin
|
||||
-- -------------------------
|
||||
-- DROP ROLE IF EXISTS admin;
|
||||
--
|
||||
--
|
||||
-- CREATE ROLE admin WITH PASSWORD 'admin';
|
||||
-- -------------------------
|
||||
-- Alter Role admin
|
||||
-- -------------------------
|
||||
-- ALTER ROLE admin WITH SUPERUSER CREATEROLE CREATEDB LOGIN;
|
||||
-- -------------------------
|
||||
-- Database GRANT PRIVILEGES
|
||||
-- -------------------------
|
||||
GRANT ALL PRIVILEGES ON DATABASE authentication TO admin;
|
||||
GRANT ALL PRIVILEGES ON DATABASE authentication_test TO admin;
|
||||
-- -------------------------
|
||||
-- Connect to authentication database
|
||||
-- -------------------------
|
||||
\c authentication;
|
||||
-- -------------------------
|
||||
-- Table users
|
||||
-- -------------------------
|
||||
DROP TABLE IF EXISTS users;
|
||||
--
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
username VARCHAR(100) UNIQUE NOT NULL,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATE,
|
||||
deleted_at DATE
|
||||
);
|
||||
2
sql/script.sh
Normal file
2
sql/script.sh
Normal file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
psql -U root -d postgres -f schema.sql
|
||||
28
src/configs/index.ts
Normal file
28
src/configs/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const database =
|
||||
process.env.ENV === 'dev'
|
||||
? process.env.POSTGRES_DB
|
||||
: process.env.POSTGRES_DB_TEST;
|
||||
|
||||
const configs = {
|
||||
env: process.env.ENV,
|
||||
host: process.env.HOST,
|
||||
port: Number(process.env.PORT),
|
||||
db_host: process.env.POSTGRES_URI,
|
||||
db_port: Number(process.env.POSTGRES_PORT),
|
||||
db_name: database,
|
||||
db_user: process.env.POSTGRES_USER,
|
||||
db_password: process.env.POSTGRES_PASSWORD,
|
||||
salt: Number(process.env.SALT_ROUNDS),
|
||||
pepper: process.env.SECRET_PEPPER,
|
||||
access_token: process.env.JWT_SECRET_ACCESS_TOKEN,
|
||||
refresh_token: process.env.JWT_SECRET_REFRESH_TOKEN,
|
||||
access_expires: process.env.JWT_ACCESS_TOKEN_EXPIRATION,
|
||||
refresh_expires: process.env.JWT_REFRESH_TOKEN_EXPIRATION,
|
||||
backend_host: process.env.BACKEND_HOST,
|
||||
frontend_host: process.env.FRONTEND_HOST
|
||||
};
|
||||
export default configs;
|
||||
6
src/controllers/auth/index.ts
Normal file
6
src/controllers/auth/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { createUser } from './register';
|
||||
import { authUser } from './login';
|
||||
import { updatePassword } from './updatePassword';
|
||||
import { refreshToken } from './refreshToken';
|
||||
|
||||
export { createUser, authUser, updatePassword, refreshToken };
|
||||
22
src/controllers/auth/login.ts
Normal file
22
src/controllers/auth/login.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Auth from '../../models/auth';
|
||||
import { signAccessToken, signRefreshToken } from '../../utils/token';
|
||||
|
||||
const auth = new Auth();
|
||||
|
||||
export const authUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await auth.authUser(req.body);
|
||||
const accessToken = await signAccessToken(response);
|
||||
const refreshToken = await signRefreshToken(response);
|
||||
res.status(200).json({
|
||||
status: true,
|
||||
data: { user: { ...response }, tokens: { accessToken, refreshToken } },
|
||||
message: 'User authenticated successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
31
src/controllers/auth/refreshToken.ts
Normal file
31
src/controllers/auth/refreshToken.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { verifyRefreshToken, signAccessToken } from '../../utils/token';
|
||||
|
||||
export const refreshToken = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { refreshToken: token } = req.body;
|
||||
if (!token) throw 'Bad request';
|
||||
|
||||
const payload = await verifyRefreshToken(token);
|
||||
const accessToken = await signAccessToken({
|
||||
// @ts-ignore
|
||||
id: payload?.id,
|
||||
// @ts-ignore
|
||||
first_name: payload?.first_name,
|
||||
// @ts-ignore
|
||||
last_name: payload?.last_name,
|
||||
// @ts-ignore
|
||||
username: payload?.username,
|
||||
// @ts-ignore
|
||||
email: payload?.email
|
||||
});
|
||||
res.status(200).json({
|
||||
data: { accessToken }
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(401).json({
|
||||
status: false,
|
||||
message: error
|
||||
});
|
||||
}
|
||||
};
|
||||
2
src/controllers/auth/register.ts
Normal file
2
src/controllers/auth/register.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { createUser } from '../users/createUser';
|
||||
export { createUser };
|
||||
19
src/controllers/auth/updatePassword.ts
Normal file
19
src/controllers/auth/updatePassword.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from 'express';
|
||||
import Auth from '../../models/auth';
|
||||
|
||||
const auth = new Auth();
|
||||
|
||||
export const updatePassword = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await auth.updatePassword(req.params.id, req.body);
|
||||
res.status(204).json({
|
||||
status: true,
|
||||
data: response,
|
||||
message: 'Password changed successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
error: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
22
src/controllers/users/createUser.ts
Normal file
22
src/controllers/users/createUser.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User from '../../models/user';
|
||||
import { signAccessToken, signRefreshToken } from '../../utils/token';
|
||||
|
||||
const user = new User();
|
||||
|
||||
export const createUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await user.createUser(req.body);
|
||||
const accessToken = await signAccessToken(response);
|
||||
const refreshToken = await signRefreshToken(response);
|
||||
res.status(201).json({
|
||||
status: true,
|
||||
data: { user: { ...response }, tokens: { accessToken, refreshToken } },
|
||||
message: 'User created successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
error: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
19
src/controllers/users/deleteUser.ts
Normal file
19
src/controllers/users/deleteUser.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User from '../../models/user';
|
||||
|
||||
const user = new User();
|
||||
|
||||
export const deleteUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await user.deleteUser(req.params.id);
|
||||
res.status(200).json({
|
||||
status: true,
|
||||
data: response,
|
||||
message: 'User deleted successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
19
src/controllers/users/getUser.ts
Normal file
19
src/controllers/users/getUser.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User from '../../models/user';
|
||||
|
||||
const user = new User();
|
||||
|
||||
export const getUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await user.getUser(req.params.id);
|
||||
res.status(200).json({
|
||||
status: true,
|
||||
data: response,
|
||||
message: 'User fetched successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
5
src/controllers/users/index.ts
Normal file
5
src/controllers/users/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { getUser } from './getUser';
|
||||
import { updateUser } from './updateUser';
|
||||
import { deleteUser } from './deleteUser';
|
||||
|
||||
export { getUser, updateUser, deleteUser };
|
||||
19
src/controllers/users/updateUser.ts
Normal file
19
src/controllers/users/updateUser.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from 'express';
|
||||
import User from '../../models/user';
|
||||
|
||||
const user = new User();
|
||||
|
||||
export const updateUser = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const response = await user.updateUser(req.params.id, req.body);
|
||||
res.status(203).json({
|
||||
status: true,
|
||||
data: response,
|
||||
message: 'User updated successfully.'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
message: (error as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
17
src/database/index.ts
Normal file
17
src/database/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Pool, PoolClient } from 'pg';
|
||||
import configs from '../configs';
|
||||
|
||||
const pool = new Pool({
|
||||
host: configs.db_host,
|
||||
port: configs.db_port,
|
||||
database: configs.db_name,
|
||||
user: configs.db_user,
|
||||
password: configs.db_password
|
||||
});
|
||||
|
||||
export default {
|
||||
connect: async (): Promise<PoolClient> => {
|
||||
const client = await pool.connect();
|
||||
return client;
|
||||
}
|
||||
};
|
||||
22
src/helpers/reporter.ts
Normal file
22
src/helpers/reporter.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import {
|
||||
DisplayProcessor,
|
||||
SpecReporter,
|
||||
StacktraceOption
|
||||
} from 'jasmine-spec-reporter';
|
||||
import SuiteInfo = jasmine.SuiteInfo;
|
||||
|
||||
class CustomProcessor extends DisplayProcessor {
|
||||
public displayJasmineStarted(info: SuiteInfo, log: string): string {
|
||||
return `${log}`;
|
||||
}
|
||||
}
|
||||
|
||||
jasmine.getEnv().clearReporters();
|
||||
jasmine.getEnv().addReporter(
|
||||
new SpecReporter({
|
||||
spec: {
|
||||
displayStacktrace: StacktraceOption.NONE
|
||||
},
|
||||
customProcessors: [CustomProcessor]
|
||||
})
|
||||
);
|
||||
5
src/middlewares/validation/auth/index.ts
Normal file
5
src/middlewares/validation/auth/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { validateRegister } from './validateRegister';
|
||||
import { validateLogin } from './validateLogin';
|
||||
import { validateUpdatePassword } from './validateUpdatePassword';
|
||||
|
||||
export { validateRegister, validateLogin, validateUpdatePassword };
|
||||
35
src/middlewares/validation/auth/validateLogin.ts
Normal file
35
src/middlewares/validation/auth/validateLogin.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { body, oneOf } from 'express-validator';
|
||||
import { validate } from '../validationResult';
|
||||
|
||||
export const validateLogin = [
|
||||
oneOf(
|
||||
[
|
||||
body('username')
|
||||
.exists()
|
||||
.withMessage('username is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('username is empty')
|
||||
.isString()
|
||||
.withMessage('username must be string'),
|
||||
body('email')
|
||||
.exists()
|
||||
.withMessage('Email is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('Email is empty')
|
||||
.isEmail()
|
||||
.withMessage('Email is not valid')
|
||||
.normalizeEmail()
|
||||
.withMessage('Email is not normalized')
|
||||
],
|
||||
'Must be at least one of email or username'
|
||||
),
|
||||
body('password')
|
||||
.exists()
|
||||
.withMessage('password is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('password is empty')
|
||||
.isLength({ min: 8 })
|
||||
.withMessage('password must be at least 8 characters long'),
|
||||
(req: Request, res: Response, next: NextFunction) => validate(req, res, next)
|
||||
];
|
||||
2
src/middlewares/validation/auth/validateRegister.ts
Normal file
2
src/middlewares/validation/auth/validateRegister.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { validateCreateUser } from '../users/validateCreateUser';
|
||||
export const validateRegister = validateCreateUser;
|
||||
19
src/middlewares/validation/auth/validateUpdatePassword.ts
Normal file
19
src/middlewares/validation/auth/validateUpdatePassword.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { check, body } from 'express-validator';
|
||||
import { validate } from '../validationResult';
|
||||
|
||||
export const validateUpdatePassword = [
|
||||
check('id')
|
||||
.exists()
|
||||
.withMessage('id is missing from the parameters')
|
||||
.notEmpty()
|
||||
.withMessage('id is empty'),
|
||||
body('password')
|
||||
.exists()
|
||||
.withMessage('password is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('password is empty')
|
||||
.isLength({ min: 8 })
|
||||
.withMessage('password must be at least 8 characters long'),
|
||||
(req: Request, res: Response, next: NextFunction) => validate(req, res, next)
|
||||
];
|
||||
5
src/middlewares/validation/users/index.ts
Normal file
5
src/middlewares/validation/users/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { validateGetUser } from './validateGetUser';
|
||||
import { validateUpdateUser } from './validateUpdateUser';
|
||||
import { validateDeleteUser } from './validateDeleteUser';
|
||||
|
||||
export { validateGetUser, validateUpdateUser, validateDeleteUser };
|
||||
46
src/middlewares/validation/users/validateCreateUser.ts
Normal file
46
src/middlewares/validation/users/validateCreateUser.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { body } from 'express-validator';
|
||||
import { validate } from '../validationResult';
|
||||
|
||||
export const validateCreateUser = [
|
||||
body('first_name')
|
||||
.exists()
|
||||
.withMessage("first_name does'nt exists in the body.")
|
||||
.notEmpty()
|
||||
.withMessage('first_name is empty')
|
||||
.isString()
|
||||
.isLength({ min: 5, max: 50 })
|
||||
.withMessage('first_name must be at least 5 and maximum 50 letters'),
|
||||
body('last_name')
|
||||
.exists()
|
||||
.withMessage("last_name does'nt exists in the body.")
|
||||
.notEmpty()
|
||||
.withMessage('last_name is empty')
|
||||
.isString()
|
||||
.isLength({ min: 5, max: 50 })
|
||||
.withMessage('last_name must be at least 5 and maximum 50 letters'),
|
||||
body('username')
|
||||
.exists()
|
||||
.withMessage('username is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('username is empty')
|
||||
.isString()
|
||||
.withMessage('username must be string'),
|
||||
body('email')
|
||||
.exists()
|
||||
.withMessage('Email is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('Email is empty')
|
||||
.isEmail()
|
||||
.withMessage('Email is not valid')
|
||||
.normalizeEmail()
|
||||
.withMessage('Email is not normalized'),
|
||||
body('password')
|
||||
.exists()
|
||||
.withMessage('password is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('password is empty')
|
||||
.isLength({ min: 8 })
|
||||
.withMessage('password must be at least 8 characters long'),
|
||||
(req: Request, res: Response, next: NextFunction) => validate(req, res, next)
|
||||
];
|
||||
12
src/middlewares/validation/users/validateDeleteUser.ts
Normal file
12
src/middlewares/validation/users/validateDeleteUser.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { check } from 'express-validator';
|
||||
import { validate } from '../validationResult';
|
||||
|
||||
export const validateDeleteUser = [
|
||||
check('id')
|
||||
.exists()
|
||||
.withMessage('id is missing from the parameters')
|
||||
.notEmpty()
|
||||
.withMessage('id is empty'),
|
||||
(req: Request, res: Response, next: NextFunction) => validate(req, res, next)
|
||||
];
|
||||
12
src/middlewares/validation/users/validateGetUser.ts
Normal file
12
src/middlewares/validation/users/validateGetUser.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { check } from 'express-validator';
|
||||
import { validate } from '../validationResult';
|
||||
|
||||
export const validateGetUser = [
|
||||
check('id')
|
||||
.exists()
|
||||
.withMessage('id is missing from the parameters')
|
||||
.notEmpty()
|
||||
.withMessage('id is empty'),
|
||||
(req: Request, res: Response, next: NextFunction) => validate(req, res, next)
|
||||
];
|
||||
44
src/middlewares/validation/users/validateUpdateUser.ts
Normal file
44
src/middlewares/validation/users/validateUpdateUser.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { check, body } from 'express-validator';
|
||||
import { validate } from '../validationResult';
|
||||
|
||||
export const validateUpdateUser = [
|
||||
check('id')
|
||||
.exists()
|
||||
.withMessage('id is missing from the parameters')
|
||||
.notEmpty()
|
||||
.withMessage('id is empty'),
|
||||
body('first_name')
|
||||
.exists()
|
||||
.withMessage("first_name does'nt exists in the body.")
|
||||
.notEmpty()
|
||||
.withMessage('first_name is empty')
|
||||
.isString()
|
||||
.isLength({ min: 5, max: 50 })
|
||||
.withMessage('first_name must be at least 5 and maximum 50 letters'),
|
||||
body('last_name')
|
||||
.exists()
|
||||
.withMessage("last_name does'nt exists in the body.")
|
||||
.notEmpty()
|
||||
.withMessage('last_name is empty')
|
||||
.isString()
|
||||
.isLength({ min: 5, max: 50 })
|
||||
.withMessage('last_name must be at least 5 and maximum 50 letters'),
|
||||
body('username')
|
||||
.exists()
|
||||
.withMessage('username is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('username is empty')
|
||||
.isString()
|
||||
.withMessage('username must be string'),
|
||||
body('email')
|
||||
.exists()
|
||||
.withMessage('Email is missing from the body')
|
||||
.notEmpty()
|
||||
.withMessage('Email is empty')
|
||||
.isEmail()
|
||||
.withMessage('Email is not valid')
|
||||
.normalizeEmail()
|
||||
.withMessage('Email is not normalized'),
|
||||
(req: Request, res: Response, next: NextFunction) => validate(req, res, next)
|
||||
];
|
||||
17
src/middlewares/validation/validationResult.ts
Normal file
17
src/middlewares/validation/validationResult.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { validationResult } from 'express-validator';
|
||||
|
||||
export const validate = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
const errorArray = errors
|
||||
.array()
|
||||
.map((error) => ({ [error.param]: [error.msg] }));
|
||||
return res.status(422).json({ errors: errorArray });
|
||||
}
|
||||
next();
|
||||
};
|
||||
2
src/middlewares/verifyToken.ts
Normal file
2
src/middlewares/verifyToken.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { verifyAccessToken } from '../utils/token';
|
||||
export const verifyToken = verifyAccessToken;
|
||||
89
src/models/auth.ts
Normal file
89
src/models/auth.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import database from '../database';
|
||||
import { PoolClient } from 'pg';
|
||||
import { compare, hash as hashPass } from '../utils/password';
|
||||
import { UserType } from './user';
|
||||
|
||||
type AuthType = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
type PasswordType = {
|
||||
old_password: string;
|
||||
new_password: string;
|
||||
};
|
||||
|
||||
class Auth {
|
||||
async withConnection<T>(
|
||||
callback: (connection: PoolClient) => Promise<T>
|
||||
): Promise<T> {
|
||||
const connection = await database.connect();
|
||||
try {
|
||||
return await callback(connection);
|
||||
} catch (error) {
|
||||
throw new Error((error as Error).message);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
async authUser(u: AuthType): Promise<AuthType & UserType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const query = {
|
||||
text: 'SELECT password FROM users WHERE email=$1',
|
||||
values: [u.email]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
if (result.rows.length) {
|
||||
const { password: hash } = result.rows[0];
|
||||
const check = await compare(u.password, hash);
|
||||
if (check) {
|
||||
const query = {
|
||||
text: 'SELECT id, first_name, last_name, username, email FROM users WHERE email=$1',
|
||||
values: [u.email]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
}
|
||||
throw new Error('Password is incorrect.');
|
||||
}
|
||||
throw new Error("Email doesn't exists.");
|
||||
});
|
||||
}
|
||||
async authMe(id: string): Promise<UserType & AuthType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const query = {
|
||||
text: 'SELECT id, first_name, last_name, username, email FROM users WHERE id=$1',
|
||||
values: [id]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
});
|
||||
}
|
||||
async updatePassword(
|
||||
id: string,
|
||||
p: AuthType & PasswordType
|
||||
): Promise<UserType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const query = {
|
||||
text: 'SELECT password FROM users WHERE id=$1',
|
||||
values: [id]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
if (result.rows.length) {
|
||||
const { password: hash } = result.rows[0];
|
||||
const check = await compare(p.old_password, hash);
|
||||
if (check) {
|
||||
const password = await hashPass(p.new_password);
|
||||
const query = {
|
||||
text: 'UPDATE users SET password=$2 WHERE id=$1 RETURNING id',
|
||||
values: [id, password]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
}
|
||||
throw new Error('Old password is incorrect.');
|
||||
}
|
||||
throw new Error("User id doesn't exists.");
|
||||
});
|
||||
}
|
||||
}
|
||||
export default Auth;
|
||||
94
src/models/tests/userSpec.ts
Normal file
94
src/models/tests/userSpec.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import database from '../../database';
|
||||
import User, { UserType } from '../user';
|
||||
|
||||
const user = new User();
|
||||
|
||||
describe('User Model', () => {
|
||||
describe('Test methods exists', () => {
|
||||
it('expects all CRUD operation methods to be exists', () => {
|
||||
expect(user.createUser).toBeDefined();
|
||||
expect(user.getUser).toBeDefined();
|
||||
expect(user.updateUser).toBeDefined();
|
||||
expect(user.deleteUser).toBeDefined();
|
||||
});
|
||||
});
|
||||
describe('Methods returns', () => {
|
||||
const newUser1 = {
|
||||
first_name: 'Adham',
|
||||
last_name: 'Haddad',
|
||||
username: 'adhamhaddad',
|
||||
email: 'adhamhaddad.dev@gmail.com',
|
||||
password: 'adham123'
|
||||
} as UserType;
|
||||
|
||||
const updateUser1 = {
|
||||
first_name: 'Adham',
|
||||
last_name: 'Ashraf',
|
||||
username: 'adhamhaddad1',
|
||||
email: 'adhamhaddad.dev@gmail.com'
|
||||
} as UserType;
|
||||
|
||||
beforeAll(async () => {
|
||||
const connection = await database.connect();
|
||||
try {
|
||||
const query = {
|
||||
text: 'DELETE FROM users;\nALTER SEQUENCE users_id_seq RESTART WITH 1'
|
||||
};
|
||||
await connection.query(query);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
afterAll(async () => {
|
||||
const connection = await database.connect();
|
||||
try {
|
||||
const query = {
|
||||
text: 'DELETE FROM users;\nALTER SEQUENCE users_id_seq RESTART WITH 1'
|
||||
};
|
||||
await connection.query(query);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
|
||||
it('createUser method should return a new user', async () => {
|
||||
const result = await user.createUser(newUser1);
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
first_name: 'Adham',
|
||||
last_name: 'Haddad',
|
||||
username: 'adhamhaddad',
|
||||
email: 'adhamhaddad.dev@gmail.com'
|
||||
} as UserType);
|
||||
});
|
||||
|
||||
it('getUser method should return Object of user', async () => {
|
||||
const result = await user.getUser('1');
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
first_name: 'Adham',
|
||||
last_name: 'Haddad',
|
||||
username: 'adhamhaddad',
|
||||
email: 'adhamhaddad.dev@gmail.com'
|
||||
} as UserType);
|
||||
});
|
||||
|
||||
it('updateUser method should return object with new values', async () => {
|
||||
const result = await user.updateUser('1', updateUser1);
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
first_name: 'Adham',
|
||||
last_name: 'Ashraf',
|
||||
username: 'adhamhaddad1',
|
||||
email: 'adhamhaddad.dev@gmail.com'
|
||||
} as UserType);
|
||||
});
|
||||
|
||||
it('deleteUser method should return object with deleted user id', async () => {
|
||||
const result = await user.deleteUser('1');
|
||||
expect(result).toEqual({
|
||||
id: 1
|
||||
} as UserType);
|
||||
});
|
||||
});
|
||||
});
|
||||
79
src/models/user.ts
Normal file
79
src/models/user.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { PoolClient } from 'pg';
|
||||
import database from '../database';
|
||||
import { hash } from '../utils/password';
|
||||
|
||||
export type UserType = {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
delete_at: Date;
|
||||
};
|
||||
class User {
|
||||
async withConnection<T>(
|
||||
callback: (connection: PoolClient) => Promise<T>
|
||||
): Promise<T> {
|
||||
const connection = await database.connect();
|
||||
try {
|
||||
return await callback(connection);
|
||||
} catch (error) {
|
||||
throw new Error((error as Error).message);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
async createUser(u: UserType): Promise<UserType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const password = await hash(u.password);
|
||||
const query = {
|
||||
text: `
|
||||
INSERT INTO users (first_name, last_name, username, email, password)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, first_name, last_name, username, email
|
||||
`,
|
||||
values: [u.first_name, u.last_name, u.username, u.email, password]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
});
|
||||
}
|
||||
async getUser(id: string): Promise<UserType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const query = {
|
||||
text: 'SELECT id, first_name, last_name, username, email FROM users WHERE id=$1',
|
||||
values: [id]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
});
|
||||
}
|
||||
async updateUser(id: string, u: UserType): Promise<UserType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const query = {
|
||||
text: `
|
||||
UPDATE users SET first_name=$2, last_name=$3, username=$4, email=$5, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=$1
|
||||
RETURNING id, first_name, last_name, username, email
|
||||
`,
|
||||
values: [id, u.first_name, u.last_name, u.username, u.email]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
});
|
||||
}
|
||||
async deleteUser(id: string): Promise<UserType> {
|
||||
return this.withConnection(async (connection: PoolClient) => {
|
||||
const query = {
|
||||
text: 'UPDATE users SET updated_at=CURRENT_TIMESTAMP, deleted_at=CURRENT_TIMESTAMP WHERE id=$1 RETURNING id',
|
||||
values: [id]
|
||||
};
|
||||
const result = await connection.query(query);
|
||||
return result.rows[0];
|
||||
});
|
||||
}
|
||||
}
|
||||
export default User;
|
||||
25
src/routes/api/auth.ts
Normal file
25
src/routes/api/auth.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Router } from 'express';
|
||||
import {
|
||||
validateRegister,
|
||||
validateLogin,
|
||||
validateUpdatePassword
|
||||
} from '../../middlewares/validation/auth';
|
||||
import {
|
||||
createUser,
|
||||
authUser,
|
||||
refreshToken,
|
||||
updatePassword
|
||||
} from '../../controllers/auth';
|
||||
import { checkAccessToken } from '../../utils/token';
|
||||
import { verifyToken } from '../../middlewares/verifyToken';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router
|
||||
.post('/register', validateRegister, createUser)
|
||||
.post('/login', validateLogin, authUser)
|
||||
.patch('/reset-password', validateUpdatePassword, verifyToken, updatePassword)
|
||||
.post('/refresh-token', refreshToken)
|
||||
.get('/check-token', checkAccessToken);
|
||||
|
||||
export default router;
|
||||
3
src/routes/api/index.ts
Normal file
3
src/routes/api/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import auth from './auth';
|
||||
import users from './users';
|
||||
export { auth, users };
|
||||
17
src/routes/api/users.ts
Normal file
17
src/routes/api/users.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Router } from 'express';
|
||||
import {
|
||||
validateGetUser,
|
||||
validateUpdateUser,
|
||||
validateDeleteUser
|
||||
} from '../../middlewares/validation/users';
|
||||
import { getUser, updateUser, deleteUser } from '../../controllers/users';
|
||||
import { verifyToken } from '../../middlewares/verifyToken';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router
|
||||
.get('/:id', validateGetUser, verifyToken, getUser)
|
||||
.patch('/:id', validateUpdateUser, verifyToken, updateUser)
|
||||
.delete('/:id', validateDeleteUser, verifyToken, deleteUser);
|
||||
|
||||
export default router;
|
||||
9
src/routes/index.ts
Normal file
9
src/routes/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Router } from 'express';
|
||||
import { auth, users } from './api';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.use('/auth', auth);
|
||||
router.use('/users', users);
|
||||
|
||||
export default router;
|
||||
48
src/server.ts
Normal file
48
src/server.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import express, { Application } from 'express';
|
||||
import helmet from 'helmet';
|
||||
import morgan from 'morgan';
|
||||
import cors from 'cors';
|
||||
import os from 'os';
|
||||
import configs from './configs';
|
||||
import router from './routes';
|
||||
|
||||
// Express App
|
||||
const app: Application = express();
|
||||
const port: number = configs.port || 8000;
|
||||
|
||||
const ip =
|
||||
os.networkInterfaces()['wlan0']?.[0].address ||
|
||||
os.networkInterfaces()['eth0']?.[0].address;
|
||||
|
||||
const corsOptions = {
|
||||
allowedHeaders: [
|
||||
'Origin',
|
||||
'X-Requested-With',
|
||||
'Content-Type',
|
||||
'Accept',
|
||||
'X-Access-Token',
|
||||
'Authorization',
|
||||
'Access-Control-Allow-Origin',
|
||||
'Access-Control-Allow-Headers',
|
||||
'Access-Control-Allow-Methods'
|
||||
],
|
||||
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
|
||||
preflightContinue: true,
|
||||
origin: '*'
|
||||
};
|
||||
|
||||
// Middlewares
|
||||
app.use(helmet());
|
||||
app.use(cors(corsOptions));
|
||||
app.use(morgan('common'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(router);
|
||||
|
||||
// Express Server
|
||||
app.listen(port, () => {
|
||||
console.log(`Backend server is listening on http://${ip}:${configs.port}`);
|
||||
console.log('Press CTRL+C to stop the server.');
|
||||
});
|
||||
|
||||
export default app;
|
||||
8
src/utils/password.ts
Normal file
8
src/utils/password.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
import configs from '../configs';
|
||||
|
||||
export const hash = (password: string) =>
|
||||
bcrypt.hash(`${configs.pepper}${password}${configs.pepper}`, configs.salt);
|
||||
|
||||
export const compare = (password: string, hash: string) =>
|
||||
bcrypt.compare(`${configs.pepper}${password}${configs.pepper}`, hash);
|
||||
179
src/utils/token.ts
Normal file
179
src/utils/token.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt, { SignOptions } from 'jsonwebtoken';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import configs from '../configs';
|
||||
import Auth from '../models/auth';
|
||||
|
||||
type Payload = {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
const privateAccessKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'accessToken',
|
||||
'private.key'
|
||||
);
|
||||
const privateRefreshKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'refreshToken',
|
||||
'private.key'
|
||||
);
|
||||
const publicAccessKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'accessToken',
|
||||
'public.key'
|
||||
);
|
||||
const publicRefreshKey = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'keys',
|
||||
'refreshToken',
|
||||
'public.key'
|
||||
);
|
||||
|
||||
export const signAccessToken = async (payload: Payload) => {
|
||||
const options: SignOptions = {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: configs.access_expires,
|
||||
issuer: 'adhamhaddad',
|
||||
audience: String(payload.id)
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(privateAccessKey, { encoding: 'utf8' }, (err, key) => {
|
||||
if (err) reject(err);
|
||||
jwt.sign(payload, key, options, (err, token) => {
|
||||
if (err) reject(err);
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyAccessToken = async (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const authorization = req.headers.authorization as string;
|
||||
if (!authorization) {
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: 'Not Authorized'
|
||||
});
|
||||
}
|
||||
const token = authorization.split(' ')[1];
|
||||
fs.readFile(publicAccessKey, 'utf8', (err, key) => {
|
||||
if (err) err.message;
|
||||
jwt.verify(token, key, { algorithms: ['RS256'] }, (err, payload) => {
|
||||
if (err)
|
||||
return res.status(401).json({
|
||||
status: false,
|
||||
message: (err as Error).message
|
||||
});
|
||||
return next();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const signRefreshToken = async (payload: Payload) => {
|
||||
const options: SignOptions = {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: configs.refresh_expires,
|
||||
issuer: 'adhamhaddad',
|
||||
audience: String(payload.id)
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(privateRefreshKey, { encoding: 'utf8' }, (err, key) => {
|
||||
if (err) reject(err);
|
||||
jwt.sign(payload, key, options, (err, token) => {
|
||||
if (err) reject(err);
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const verifyRefreshToken = async (refreshToken: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(publicRefreshKey, 'utf8', (err, key) => {
|
||||
if (err) reject(err);
|
||||
jwt.verify(
|
||||
refreshToken,
|
||||
key,
|
||||
{ algorithms: ['RS256'] },
|
||||
(err, payload) => {
|
||||
if (err) reject(err);
|
||||
resolve(payload);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const checkAccessToken = async (req: Request, res: Response) => {
|
||||
const auth = new Auth();
|
||||
try {
|
||||
const authorization = req.headers.authorization as string;
|
||||
if (!authorization) {
|
||||
res.status(401).json({
|
||||
status: false,
|
||||
message: 'Not Authorized'
|
||||
});
|
||||
}
|
||||
const token = authorization.split(' ')[1];
|
||||
const authMe = async (id: string) => await auth.authMe(id);
|
||||
fs.readFile(publicAccessKey, 'utf8', (err, key) => {
|
||||
if (err) err.message;
|
||||
jwt.verify(token, key, { algorithms: ['RS256'] }, (err, payload) => {
|
||||
if (err) {
|
||||
const decode = jwt.decode(token, { complete: true });
|
||||
// @ts-ignore
|
||||
const id = decode?.payload?.aud;
|
||||
const responseData = async () => {
|
||||
const response = await authMe(id);
|
||||
const accessToken = await signAccessToken(response);
|
||||
return res.status(200).json({
|
||||
status: true,
|
||||
data: {
|
||||
response,
|
||||
accessToken
|
||||
},
|
||||
message: 'Access token generated successfully.'
|
||||
});
|
||||
};
|
||||
responseData();
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const id = payload.id;
|
||||
const responseData = async () => {
|
||||
const response = await authMe(id);
|
||||
res.status(200).json({
|
||||
data: response
|
||||
});
|
||||
};
|
||||
responseData();
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
status: false,
|
||||
message: (err as Error).message
|
||||
});
|
||||
}
|
||||
};
|
||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["node_module", "./dist", "./uploads"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user