Skip to content

Commit

Permalink
Merge pull request #49 from Gather307/user-auth
Browse files Browse the repository at this point in the history
Merges User Auth into Main
  • Loading branch information
SilveerDusk authored May 22, 2024
2 parents 3dbf4a0 + 69e713c commit 0c7f777
Show file tree
Hide file tree
Showing 16 changed files with 1,364 additions and 484 deletions.
92 changes: 46 additions & 46 deletions .github/workflows/azure-static-web-apps-thankful-tree-04ab28e1e.yml
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
name: Azure Static Web Apps CI/CD

on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main

jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v3
with:
submodules: true
lfs: false
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_TREE_04AB28E1E }}
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
action: "upload"
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "./frontend" # App source code path
api_location: "" # Api source code path - optional
output_location: "build" # Built app content directory - optional
###### End of Repository/Build Configurations ######

close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close Pull Request Job
steps:
- name: Close Pull Request
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_TREE_04AB28E1E }}
action: "close"
name: Azure Static Web Apps CI/CD

on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, closed]
branches:
- main

jobs:
build_and_deploy_job:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v3
with:
submodules: true
lfs: false
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_TREE_04AB28E1E }}
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
action: "upload"
###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
app_location: "./frontend" # App source code path
api_location: "" # Api source code path - optional
output_location: "build" # Built app content directory - optional
###### End of Repository/Build Configurations ######

close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
name: Close Pull Request Job
steps:
- name: Close Pull Request
id: closepullrequest
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_THANKFUL_TREE_04AB28E1E }}
action: "close"
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
node_modules/
node_modules*
.vercel
.env*


87 changes: 87 additions & 0 deletions backend/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import User, { IUser } from "./models/userSchema";
import dotenv from "dotenv";
import { Request, Response } from "express";
import connectDB from "./connection";

dotenv.config();

type User = { username: string; hashedPassword: string };
const creds = [User]; // username, hashedPassword

export function authenticateUser(req: Request, res: Response, next: any) {
const authHeader = req.headers["authorization"];
//Getting the 2nd part of the auth header (the token)
const token = authHeader && authHeader.split(" ")[1];

if (!token) {
console.log("No token received");
res.status(401).end();
} else {
jwt.verify(
token,
process.env.TOKEN_SECRET as jwt.Secret,
(error, decoded) => {
if (decoded) {
next();
} else {
console.log("JWT error:", error);
res.status(401).end();
}
},
);
}
}

export const loginUser = async (req: Request, res: Response) => {
connectDB();
const { username, password } = req.body; // from form
const existingUser = await User.findOne({ username }).orFail();
console.log("Existing user:", existingUser);

if (existingUser == null) {
// invalid username
res.status(401).send("Unauthorized: Not a user");
} else {
try {
console.log("Comparing passwords");
console.log(password, existingUser.password);
const matched = await bcrypt.compare(password, existingUser.password);
console.log("Password matched:", matched);
if (matched) {
const token = await generateAccessToken(username);
console.log("Token generated:", token);
res.status(200).send({ existingUser, token });
} else {
// invalid password
console.log("Invalid password");
res.status(401).send("Unauthorized: Invalid password");
}
} catch (error) {
console.log("Failed to authenticate user");
res.status(401).send("Unauthorized: Failed to authenticate user");
}
}
};

function generateAccessToken(username: any) {
return new Promise((resolve, reject) => {
jwt.sign(
{ username: username },
process.env.TOKEN_SECRET as jwt.Secret,
{ expiresIn: "1d" },
(error: Error | null, token: string | undefined) => {
if (error) {
reject(error);
} else if (token) {
resolve(token);
} else {
reject(new Error("Token generation failed"));
}
},
);
});
}

export { generateAccessToken };
4 changes: 3 additions & 1 deletion backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { userEndpoints } from "./routes/userRoutes";
import { groupEndpoints } from "./routes/groupRoutes";
import { basketEndpoints } from "./routes/basketRoutes";
import { itemEndpoints } from "./routes/itemRoutes";
import { loginUser } from "./auth";

const app: Express = express();
app.use(express.json());
Expand All @@ -12,7 +13,7 @@ app.use((req: Request, res: Response, next: NextFunction) => {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept",
"Origin, X-Requested-With, Content-Type, Accept, Authorization",
);
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE, PUT");
next();
Expand All @@ -26,6 +27,7 @@ function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
app.use(loggerMiddleware);

// Routes
app.post("/login", loginUser as any);
app.use("/users", userEndpoints);
app.use("/groups", groupEndpoints);
app.use("/baskets", basketEndpoints);
Expand Down
2 changes: 2 additions & 0 deletions backend/models/userSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type IUser = {
_id: Schema.Types.ObjectId;
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
groups: Schema.Types.ObjectId[] | null;
Expand All @@ -18,6 +19,7 @@ export type IUser = {
const UserSchema = new Schema<IUser>({
username: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
firstName: { type: String, required: true },
lastName: { type: String, required: true },
groups: { type: [Schema.Types.ObjectId], required: true, default: [] },
Expand Down
9 changes: 7 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,23 @@
"author": "",
"license": "ISC",
"dependencies": {
"@types/mongoose": "^5.11.97",
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.3.4",
"node": "^20.13.0",
"prettier": "^3.2.5",
"ts-node": "^10.9.2"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.11",
"@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^7.9.0",
"@typescript-eslint/parser": "^7.10.0",
"@typescript-eslint/eslint-plugin": "^7.10.0",
"eslint": "^9.3.0",
"nodemon": "^3.1.0"
}
Expand Down
60 changes: 51 additions & 9 deletions backend/routes/userRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import express from "express";
import { Request, Response } from "express";
import User, { IUser } from "../models/userSchema";
import connectDB from "../connection";
import { authenticateUser, generateAccessToken } from "../auth";
import bcrypt from "bcrypt";

const router = express.Router();

Expand All @@ -20,45 +22,85 @@ router.get("/", async (req: Request, res: Response) => {
}
});

router.get("/:id", async (req: Request, res: Response) => {
connectDB();
const { id } = req.params;

try {
const user = await User.findById(id);

if (!user) {
res.status(404).send("User not found");
} else {
res.send(user);
}
} catch (error) {
res.status(500).send("Internal Server Error");
}
});

router.post("/", async (req: Request, res: Response) => {
connectDB();
let { username, email, password, firstName, lastName } = req.body;
let hashedPassword = "";
let token: any;

try {
console.log("Creating a new user with data:", req.body);
//Create new User to add
const { username, email, firstName, lastName } = req.body;
if (!username || !email || !firstName || !lastName) {
console.log(req.body);
if (!username || !email || !password || !firstName || !lastName) {
console.error("Missing required fields", req.body);
return res.status(400).send("Missing required fields");
}

try {
console.log("Registering user");
const existingUser = await User.findOne({ username }).lean();
if (existingUser != null) {
return res.status(400).send("User already exists");
} else {
const salt = await bcrypt.genSalt(10);
if (salt) {
hashedPassword = await bcrypt.hash(password, salt);
token = await generateAccessToken(username);
}
}
} catch (error) {
console.log("Error:", error);
}

if (hashedPassword === "") {
return res.status(400).send("Failed to Register User");
}
console.log("Adding user to database");

const userToAdd = new User({
username,
email,
password: hashedPassword,
firstName,
lastName,
});

const newUser = await userToAdd.save();
console.log("New user created:", newUser);
res.status(201).send(newUser);
res.status(201).send({ newUser, token });
} catch (error) {
console.error("Error adding the user:", error);
res.status(500).send("Internal Server Error");
res.status(500).send("Internal Server Error in Post");
}
});

router.patch("/:id", async (req: Request, res: Response) => {
connectDB();
// Get user ID from URL
const { id } = req.params;
const updatedData: Partial<IUser> = req.body; //Not a full update only partial

try {
connectDB();

const updatedUser = await User.findByIdAndUpdate(id, updatedData, {
new: true,
runValidators: true,
}).lean();

if (!updatedUser) {
return res.status(404).send("User not found");
}
Expand Down
1 change: 1 addition & 0 deletions frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
Expand Down
1 change: 0 additions & 1 deletion frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"dotenv": "^16.4.5",
"framer-motion": "^11.1.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
Loading

0 comments on commit 0c7f777

Please sign in to comment.