Skip to content

Commit

Permalink
In-memory authentication and authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
amrebada committed Jan 3, 2020
1 parent cf41557 commit 5d3ba66
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 2 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
PORT=
DB_URL=
DB_URL=
TOKEN_SECRET=
REFRESH_SECRET=
96 changes: 96 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@
"prototype"
],
"dependencies": {
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"dotenv": "8.2.0",
"express": "4.17.1",
"express-graphql": "0.9.0",
"graphql": "14.5.8",
"jsonwebtoken": "8.5.1",
"mongoose": "5.8.3"
},
"devDependencies": {
Expand Down
4 changes: 3 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ config();

export default {
PORT: process.env.PORT || 5000,
DB_URL: process.env.DB_URL || ""
DB_URL: process.env.DB_URL || "",
TOKEN_SECRET: process.env.TOKEN_SECRET || "",
REFRESH_SECRET: process.env.REFRESH_SECRET || ""
};
79 changes: 79 additions & 0 deletions src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { compareSync, hashSync } from "bcryptjs";
import { sign, verify } from "jsonwebtoken";
import utils from "../utils";
import config from "../config";

const { apiError, ErrorTypes } = utils;
const users = [];

const generateFullToken = email => {
const accessToken = sign({ email }, config.TOKEN_SECRET, {
expiresIn: "1h"
});
const refreshToken = sign({ email }, config.REFRESH_SECRET, {
expiresIn: "30d"
});

return {
accessToken,
refreshToken
};
};

const login = (email, password) => {
const user = users.find(v => v.email === email);
if (!user) {
throw apiError(ErrorTypes.NOT_FOUND, " this user not signed up");
}

if (!compareSync(password, user.password)) {
throw apiError(ErrorTypes.NOT_AUTHORIZED, "wrong credentials");
}

return generateFullToken(email);
};

const signUp = (email, password, cpassword) => {
const user = users.find(v => v.email === email);
if (user) {
throw apiError(ErrorTypes.CONFLICT, " this user signed up before");
}
if (password !== cpassword) {
throw apiError(ErrorTypes.BAD_REQUEST, "password not matched");
}
const hash = hashSync(password, 10);

users.push({
email,
password: hash
});

return generateFullToken(email);
};

const refresh = refreshToken => {
const payload = verify(refreshToken, config.REFRESH_SECRET);
const newAccessToken = sign(payload, config.TOKEN_SECRET, {
expiresIn: "1h"
});
return {
accessToken: newAccessToken,
refreshToken
};
};

const verifyUser = token => {
const payload = verify(token, config.TOKEN_SECRET);
const user = users.find(v => v.email === payload.email);
if (!user) {
return false;
}
return true;
};

export default {
login,
signUp,
refresh,
verifyUser
};
7 changes: 7 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import express from "express";
import graphQlHTTP from "express-graphql";
import bodyParser from "body-parser";
import schema from "./schemas";
import dbInit from "./models/index";
import config from "./config";
import auth from "./routes/auth";

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use("/", auth);

app.use(
"/graphql",
graphQlHTTP({
Expand Down
76 changes: 76 additions & 0 deletions src/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable consistent-return */
import { Router } from "express";
import utils from "../utils";
import controller from "../controllers/auth.controller";

const router = Router();
const { apiError, apiResponse, ErrorTypes, getToken } = utils;
const { login, signUp, refresh, verifyUser } = controller;

router.post("/login", (req, res) => {
try {
const { email, password } = req.body;

if (!email || !password) {
throw apiError(ErrorTypes.BAD_REQUEST, "email or password not passed");
}

const token = login(email, password);

return res.json(apiResponse(token));
} catch (error) {
res.json(apiResponse(null, error));
}
});

router.post("/signup", (req, res) => {
try {
const { email, password, cpassword } = req.body;

if (!email || !password || !cpassword) {
throw apiError(
ErrorTypes.BAD_REQUEST,
"email , password or cpassword params not found"
);
}
const token = signUp(email, password, cpassword);
return res.json(apiResponse(token));
} catch (error) {
res.json(apiResponse(null, error));
}
});

router.get("/token", (req, res) => {
try {
const { refreshToken } = req.query;

if (!refreshToken) {
throw apiError(ErrorTypes.BAD_REQUEST, "refreshToken param not found");
}
const token = refresh(refreshToken);
return res.json(apiResponse(token));
} catch (error) {
res.json(apiResponse(null, error));
}
});
router.use("/graphql", (req, res, next) => {
try {
const token = getToken(req.headers);

if (!token) {
throw apiError(ErrorTypes.FORBIDDEN, "access token is not correct");
}
const isUser = verifyUser(token);
if (!isUser) {
throw apiError(
ErrorTypes.NOT_AUTHORIZED,
"access token is expired or not correct"
);
}
next();
} catch (error) {
res.json(apiResponse(null, error));
}
});

export default router;
39 changes: 39 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const getToken = headers => {
const { authorization } = headers;
if (!authorization) {
return null;
}
const token = authorization.replace("Bearer ", "");
return token.trim();
};

const ErrorTypes = {
FORBIDDEN: "403",
BAD_REQUEST: "400",
NOT_AUTHORIZED: "401",
NOT_FOUND: "404",
CONFLICT: "409",
INTERNAL_ERROR: "500"
};

const apiError = (code, message) => {
return {
code,
message
};
};

const apiResponse = (data, error) => {
return {
success: !error,
data,
error
};
};

export default {
getToken,
ErrorTypes,
apiResponse,
apiError
};

0 comments on commit 5d3ba66

Please sign in to comment.