Merge pull request #3 from JBB0807/auth-deploy

Auth deploy
This commit is contained in:
JB Balahadia 2025-04-25 14:02:12 -07:00 committed by GitHub
commit 305527db3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 3351 additions and 10 deletions

View file

@ -4,7 +4,9 @@
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
# DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" #use this when testing local, remmber to run the proxy command
# DATABASE_URL="postgres://postgres:w2eSd47GJEdqvMf@snakebyte.internal:5432" # DATABASE_URL="postgresql://postgres:wly9H8gjjmxYfg1@localhost:15432/postgres?schema=public"
DATABASE_URL=postgres://snakebyte:zVB7lgOiKr89dq6@localhost:5432/snakebyte?sslmode=disable
DATABASE_URL="postgres://postgres:wly9H8gjjmxYfg1@bytecamp-db.flycast:5432?sslmode=disable"
# DATABASE_URL=postgres://snakebyte:zVB7lgOiKr89dq6@localhost:5432/snakebyte?sslmode=disable
NODE_PORT=3000 NODE_PORT=3000

2
assignment-service/.env Normal file
View file

@ -0,0 +1,2 @@
#DB_ASSIGNMENT_SERVICE_URL = "http://localhost:3000/"
DB_ASSIGNMENT_SERVICE_URL = "http://db-assignment-service.internal:3000/"

View file

@ -0,0 +1,50 @@
const intructorRouter = require("express").Router();
const passport = require("passport");
const axios = require("axios");
const { DB_ASSIGNMENT_SERVICE_URL } = process.env.DB_ASSIGNMENT_SERVICE_URL || "http://localhost:3000";
//
intructorRouter.post("/create", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
const response = await axios.post(`${DB_ASSIGNMENT_SERVICE_URL}/assignments`, req.body);
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({ error: error.message });
}
});
// This endpoint is for instructors to get a list of assignments they have created
intructorRouter.get("/list", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
const instructorId = req.user.id; // Assuming req.user contains the authenticated user
const response = await axios.get(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/instructor/${instructorId}`);
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({ error: error.message });
}
});
// This endpoint is for instructors to update an assignment
intructorRouter.put("/update/:id", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
const assignmentId = req.params.id;
const response = await axios.put(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`, req.body);
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({ error: error.message });
}
});
// This endpoint is for instructors to delete an assignment
intructorRouter.delete("/delete/:id", passport.authenticate("jwt", { session: false }), async (req, res) => {
try {
const assignmentId = req.params.id;
const response = await axios.delete(`${DB_ASSIGNMENT_SERVICE_URL}/assignments/${assignmentId}`);
res.status(response.status).json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({ error: error.message });
}
});
module.exports = intructorRouter;

View file

@ -0,0 +1,40 @@
# Node.js specific
node_modules/
npm-debug.log
yarn-debug.log
# Environment and secrets
.env*
*.env
*.pem
*.key
*.crt
# Development artifacts
.idea/
.vscode/
*.swp
*.swo
dist/
build/
out/
# Version control
.git/
.gitignore
# Temporary files
tmp/
temp/
*.tmp
# Documentation
*.md
README*
# Docker files
Dockerfile*
docker-compose*
# Project-specific
package-lock.json

10
auth-service/.env Normal file
View file

@ -0,0 +1,10 @@
GOOGLE_CLIENT_ID = "485880105639-1in8tvb6ondnn198rasuj2d8ank06ntp.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET = "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"
GOOGLE_CALLBACK_URL = "https://byte-camp-auth-service.fly.dev/auth/google/callback"
LOGIN_REDIRECT_URL = "https://bytecamp-web.fly.dev/"
#DB_USER_SERVICE_URL = "http://localhost:3000/"
DB_USER_SERVICE_URL = "http://db-user-service.internal:3000/"
AUTH_SESSION_KEY = "f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42"
fly secrets set GOOGLE_CALLBACK_URL=https://byte-camp-auth-service.fly.dev/auth/google/callback
#fly secrets set GOOGLE_CLIENT_ID=485880105639-1in8tvb6ondnn198rasuj2d8ank06ntp.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=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 LOGIN_REDIRECT_URL=https://bytecamp-web.fly.dev/ DB_USER_SERVICE_URL=https://db-user-service.fly.dev:3000/ AUTH_SESSION_KEY=f3f4d8e6b17a4b3abdc8e9a2c0457aaf91c0d5f6e3b7a9c8df624bd71ea35f42

23
auth-service/Dockerfile Normal file
View file

@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1
# Use the official Node.js image as the base image
ARG NODE_VERSION=22.13.1
FROM node:${NODE_VERSION}-slim AS base
# Set the working directory
WORKDIR /app
COPY . .
RUN npm install
# Install dependencies using npm ci for deterministic builds
# RUN --mount=type=cache,target=/root/.npm npm ci --production
# Copy the application source code
COPY --link . .
# Expose the application port
EXPOSE 8080
# Define the command to run the application
CMD ["node", "server.js"]

33
auth-service/README.md Normal file
View file

@ -0,0 +1,33 @@
# Running the Project with Docker
This section provides instructions to build and run the project using Docker.
## Prerequisites
- Ensure Docker and Docker Compose are installed on your system.
- The project requires Node.js version `22.13.1` as specified in the Dockerfile.
## Environment Variables
- If applicable, create a `.env` file in the project root directory to define environment variables. Uncomment the `env_file` line in the `docker-compose.yml` file to enable this.
## Build and Run Instructions
1. Build the Docker image and start the services:
```bash
docker-compose up --build
```
2. Access the application at `http://localhost:8080`.
## Configuration
- The application runs with a non-root user for enhanced security.
- The `NODE_ENV` is set to `production` and `NODE_OPTIONS` is configured for optimized memory usage.
## Exposed Ports
- The application service exposes port `8080` to the host system.
For further details, refer to the provided `Dockerfile` and `docker-compose.yml` files.

42
auth-service/fly.toml Normal file
View file

@ -0,0 +1,42 @@
# fly.toml app configuration file generated for byte-camp-auth-service on 2025-04-21T14:38:25-07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = 'byte-camp-auth-service'
primary_region = 'sea'
[build]
[env]
PORT = '8080'
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = 'stop'
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[services]]
protocol = 'tcp'
internal_port = 8080
[[services.ports]]
port = 80
handlers = ['http']
[[services.ports]]
port = 443
handlers = ['tls', 'http']
[[services.tcp_checks]]
interval = '10s'
timeout = '2s'
grace_period = '5s'
[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1

1555
auth-service/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
auth-service/package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "auth-service",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"axios": "^1.8.4",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"express-session": "^1.18.1",
"nodemon": "^3.1.9",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0"
}
}

36
auth-service/passport.js Normal file
View file

@ -0,0 +1,36 @@
require('dotenv').config();
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const passport = require("passport");
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
scope: ["profile", "email"],
},
function (accessToken, refreshToken, profile, callback) {
// Save the user info to your DB here if still not yet saved
// Example of what profile might contain:
// {
// "id": "112233445566778899",
// "displayName": "John Doe",
// "emails": [{ "value": "john.doe@gmail.com" }],
// "photos": [{ "value": "https://.../photo.jpg" }]
// }
callback(null, profile);
}
)
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null,user);
});

View file

@ -0,0 +1,48 @@
const router = require("express").Router();
const passport = require("passport");
const axios = require("axios");
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: "/auth/login",
failureRedirect: "/auth/login/failed",
})
);
router.get("/login", (req, res) => {
if (req.user) {
console.log(`${process.env.DB_USER_SERVICE_URL}instructor/register-user`)
axios.post(`${process.env.DB_USER_SERVICE_URL}instructor/register-user`, {
user: req.user,
})
.then(response => {
console.log("User registration response:", response.data);
res.redirect(process.env.LOGIN_REDIRECT_URL);
})
.catch(error => {
console.error("Error registering user:", error.message);
res.status(500).json({ error: true, message: "User login failed" });
});
} else {
res.status(403).json({ error: true, message: "Not Authorized" });
}
});
router.get("/login/failed", (req, res) => {
res.status(401).json({
error: true,
message: "Log in failure",
});
});
router.get("/google", passport.authenticate("google", ["profile", "email"]));
router.get("/logout", (req, res) => {
req.logOut();
res.redirect(process.env.LOGIN_REDIRECT_URL);
});
module.exports = router;

37
auth-service/server.js Normal file
View file

@ -0,0 +1,37 @@
require('dotenv').config();
const cors = require("cors");
const express = require("express");
const passport = require("passport");
const passportSetup = require("./passport");
const authRoute = require("./routes/auth");
const session = require("express-session");
const app = express();
app.use(
session({
secret: process.env.AUTH_SESSION_KEY,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, // 1 day
},
})
);
app.use(passport.initialize());
app.use(passport.session());
app.use(
cors({
origin: "https://bytecamp-web.fly.dev",
methods: "GET",
credentials: true,
})
)
app.use("/auth", authRoute);
const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Listening on port ${port}...`));

View file

@ -1 +0,0 @@
PORT=3000

View file

@ -1,4 +0,0 @@
DATABASE_SERVICE_URL=""
FRONTEND_SERVICE_URL=""
UPLOADER_SERVICE_URL=""

View file

@ -1,2 +0,0 @@
FLY_API_BASE_URL=https://api.fly.io
FLY_API_TOKEN=your-api-token

12
user-db-service/.env Normal file
View file

@ -0,0 +1,12 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
#use this when testing local, remmber to run the proxy command
# DATABASE_URL="postgresql://postgres:wly9H8gjjmxYfg1@localhost:15432/postgres?schema=public"
DATABASE_URL="postgres://postgres:wly9H8gjjmxYfg1@bytecamp-db.flycast:5432?sslmode=disable"
# DATABASE_URL=postgres://snakebyte:zVB7lgOiKr89dq6@localhost:5432/snakebyte?sslmode=disable
NODE_PORT=3000

View file

@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1
# Use the official Node.js image as the base image
ARG NODE_VERSION=22.13.1
FROM node:${NODE_VERSION}-slim AS base
# Set the working directory
WORKDIR /app
COPY . .
RUN apt-get update -y && apt-get install -y openssl && npm install && npx prisma generate
# Copy the application source code
COPY --link . .
# Expose the application port
EXPOSE 3000
# Define the command to run the application
CMD ["node", "app.js"]

24
user-db-service/app.js Normal file
View file

@ -0,0 +1,24 @@
const express = require('express');
const { PrismaClient } = require('@prisma/client');
const app = express();
const adminRouter = require("./routes/AdminRouter");
const instructorRouter = require("./routes/InstructorRouter");
const studentRouter = require("./routes/StudentRouter");
// require('dotenv').config(); // prisma client already loads .env apparently, double check before deploying
const port = process.env.NODE_PORT; // Use env for port
console.log('NODE_PORT:', port);
const prisma = new PrismaClient();
app.use(express.json());
//use routes of other pages
app.use("/student", studentRouter);
app.use("/admin", adminRouter);
app.use("/instructor", instructorRouter);
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

45
user-db-service/fly.toml Normal file
View file

@ -0,0 +1,45 @@
# fly.toml file for a Node.js service that connects to an external Postgres DB
app = "db-user-service"
primary_region = "sea"
[build]
# Only needed if you're using a Dockerfile — can be empty if using buildpacks or Node preset
#[env]
#NODE_ENV = "production"
#DATABASE_URL = "postgresql://user:password@hostname:port/dbname"
# you can also leave DATABASE_URL unset here and use secrets instead
# Removed the [http_service] section to disable public HTTP/HTTPS access
# [http_service]
# internal_port = 3000
# force_https = true
# auto_stop_machines = true
# auto_start_machines = true
# min_machines_running = 0
# processes = ["app"]
[[services]]
protocol = "tcp"
internal_port = 3000
internal_only = true # Makes this service only accessible internally
# Removed public port exposure
# [[services.ports]]
# port = 80
# handlers = ["http"]
# [[services.ports]]
# port = 443
# handlers = ["tls", "http"]
[services.concurrency]
type = "requests"
hard_limit = 1000
soft_limit = 500
[[vm]]
memory = '1gb'
cpu_kind = "shared"
cpus = 1

1217
user-db-service/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
{
"dependencies": {
"@prisma/client": "^6.1.0",
"express": "^5.1.0",
"nodemon": "^3.1.9",
"prisma": "^6.1.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon app.js"
}
}

View file

@ -0,0 +1,19 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "windows"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model users {
userid Int @id @default(autoincrement())
email String @unique
name String?
password String?
role String?
googleid String?
logintype String?
}

View file

@ -0,0 +1,4 @@
# database microservice
- store database tables
- issue and re-issue auth tokens (jwt)
- handle password reset into db

View file

@ -0,0 +1,25 @@
const express = require("express");
const adminRouter = express.Router();
// Endpoint to fetch custom query (avoid raw queries if possible)
adminRouter.get('/query', async (req, res) => {
// double check if user is admin first from jwt
const query = req.body.query
const response = prisma.$queryRaw`${query}`;
res.status(400).json({ error: 'Custom queries are not supported.' });
});
// Fetch top users (update logic as per your requirements)
adminRouter.get('/update', async (req, res) => {
try {
const users = await prisma.user.findMany({
orderBy: { username: 'asc' }, // Example sorting
});
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = adminRouter;

View file

@ -0,0 +1,53 @@
const express = require("express");
const instructorRouter = express.Router();
const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
// For new users sign-up via Google oAuth:
instructorRouter.post("/register-user", async (req, res) => {
try {
console.log("Received request to register user");
const { id, displayName, emails } = req.body.user;
console.log("User details from request:", { id, displayName, emails });
const email = emails[0].value;
console.log("Extracted email:", email);
// Check if user exists
const user = await prisma.users.findFirst({
where: {
email: {
equals: email,
mode: "insensitive",
},
},
});
console.log("User lookup result:", user);
// if it is a new user, insert it into the DB
if (!user) {
console.log("User does not exist, creating new user");
const newUser = await prisma.users.create({
data: {
name: displayName,
email: email,
role: "instructor",
googleid: id,
logintype: "google",
},
});
console.log("New user created:", newUser);
res.json({ message: "User added successfully", user: newUser });
} else {
console.log("User already exists:", user);
res.json({ message: "User exist", user: user });
}
} catch (err) {
console.error("Error during user registration:", err.message);
res.status(500).json({ error: err.message });
}
});
module.exports = instructorRouter;

View file

@ -0,0 +1,17 @@
const express = require("express");
const studenRouter = express.Router();
// Add a new student
studenRouter.post('/add-user', async (req, res) => {
try {
const { username, email } = req.body;
const newUser = await prisma.user.create({
data: { username, email },
});
res.json({ message: 'User added successfully', user: newUser });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = studenRouter;